Merge remote-tracking branch 'odoo/7.0' into 7.0 7.0_community
authorOCA git bot <OCA-git-bot@therp.nl>
Tue, 25 Nov 2014 22:42:43 +0000 (23:42 +0100)
committerOCA git bot <OCA-git-bot@therp.nl>
Tue, 25 Nov 2014 22:42:43 +0000 (23:42 +0100)
128 files changed:
.coveragerc [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
README.md [new file with mode: 0644]
addons/account/account.py
addons/account/account_analytic_line.py
addons/account/account_invoice.py
addons/account/account_invoice_view.xml
addons/account/account_invoice_workflow.xml
addons/account/account_move_line.py
addons/account/account_view.xml
addons/account/demo/account_demo.xml
addons/account/report/account_aged_partner_balance.py
addons/account/report/account_tax_report.py
addons/account/report/account_tax_report.rml
addons/account/wizard/account_report_aged_partner_balance.py
addons/account/wizard/account_report_aged_partner_balance_view.xml
addons/account/wizard/account_report_common.py
addons/account/wizard/account_vat_view.xml
addons/account_analytic_analysis/account_analytic_analysis_view.xml
addons/account_asset/account_asset.py
addons/account_budget/account_budget.py
addons/account_payment/account_payment_view.xml
addons/account_payment/wizard/account_payment_order.py
addons/analytic/analytic_view.xml
addons/analytic_contract_hr_expense/analytic_contract_hr_expense_view.xml
addons/analytic_user_function/analytic_user_function_view.xml
addons/base_calendar/base_calendar.py
addons/crm/crm_lead_view.xml
addons/crm/report/crm_phonecall_report.py
addons/crm/report/crm_phonecall_report_view.xml
addons/delivery/__openerp__.py
addons/delivery/report/shipping.rml
addons/delivery/stock.py
addons/delivery/test/delivery_tracking_ref_backorder.yml [new file with mode: 0644]
addons/email_template/email_template.py
addons/hr_expense/hr_expense.py
addons/hr_expense/hr_expense_view.xml
addons/hr_holidays/hr_holidays.py
addons/hr_timesheet/hr_timesheet.py
addons/hr_timesheet/hr_timesheet_view.xml
addons/hr_timesheet_invoice/hr_timesheet_invoice.py
addons/hr_timesheet_invoice/tests/__init__.py [new file with mode: 0644]
addons/hr_timesheet_invoice/tests/test_multi_company.py [new file with mode: 0644]
addons/hr_timesheet_sheet/hr_timesheet_sheet.py
addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
addons/l10n_br/account_view.xml
addons/l10n_br/data/account_tax_template.xml
addons/l10n_it/data/account.account.template.csv
addons/l10n_it/data/account.tax.template.csv
addons/l10n_nl/account_chart_netherlands.xml
addons/l10n_pl/account_chart.xml
addons/mail/mail_thread.py
addons/mrp/mrp.py
addons/mrp/mrp_view.xml
addons/mrp/security/mrp_security.xml
addons/mrp/test/bom_with_service_type_product.yml
addons/mrp/test/multicompany.yml [new file with mode: 0644]
addons/mrp_byproduct/mrp_byproduct.py
addons/procurement/procurement.py
addons/product/report/product_pricelist.py
addons/product/wizard/product_price.py
addons/product/wizard/product_price_view.xml
addons/product_manufacturer/product_manufacturer_view.xml
addons/project/__openerp__.py
addons/project/project.py
addons/project/test/hours_process.yml [new file with mode: 0644]
addons/project_issue/project_issue.py
addons/project_timesheet/project_timesheet.py
addons/purchase/purchase.py
addons/purchase/stock_view.xml
addons/purchase_requisition/purchase_requisition.py
addons/purchase_requisition/test/cancel_purchase_requisition.yml
addons/purchase_requisition/test/purchase_requisition.yml
addons/purchase_requisition/test/purchase_requisition_demo.yml
addons/report_webkit/report_helper.py
addons/report_webkit/webkit_report.py
addons/sale_stock/sale_stock.py
addons/sale_stock/sale_stock_view.xml
addons/sale_stock/stock.py
addons/stock/__openerp__.py
addons/stock/migrations/7.0.1.1.1/pre-rename_sequence_code.py [new file with mode: 0644]
addons/stock/product_view.xml
addons/stock/report/report_stock.py
addons/stock/report/report_stock_view.xml
addons/stock/stock.py
addons/stock/stock_sequence.xml
addons/stock/stock_view.xml
addons/stock/test/multicompany.yml [new file with mode: 0644]
addons/stock/test/stock_move_chain_validation.yml [new file with mode: 0644]
addons/stock/wizard/stock_fill_inventory.py
addons/stock/wizard/stock_invoice_onshipping_view.xml
addons/stock/wizard/stock_return_picking.py
addons/stock_location/stock_location.py
addons/survey/__openerp__.py
addons/survey/security/ir.model.access.csv
addons/survey/security/survey_security.xml
addons/survey/survey_report.xml
addons/survey/survey_view.xml
addons/survey/test/draft2open2close_survey.yml
addons/survey/wizard/survey_answer.py
addons/survey/wizard/survey_send_invitation.py
addons/survey/wizard/survey_send_invitation.xml
addons/web/controllers/main.py
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
addons/web/static/src/js/data.js
addons/web/static/src/js/view_form.js
addons/web/static/src/js/view_list.js
addons/web/static/src/js/views.js
addons/web_calendar/static/src/js/calendar.js
addons/web_kanban/static/src/js/kanban.js
openerp/addons/base/ir/ir_attachment.py
openerp/addons/base/res/res_bank.py
openerp/addons/base/res/res_bank_view.xml
openerp/addons/base/res/res_country_data.xml
openerp/addons/base/res/res_lang.py
openerp/addons/base/res/res_partner.py
openerp/modules/loading.py
openerp/osv/orm.py
openerp/osv/osv.py
openerp/report/render/rml2pdf/trml2pdf.py
openerp/report/render/rml2pdf/utils.py
openerp/service/workers.py
openerp/service/wsgi_server.py
openerp/tools/assertion_report.py
openerp/tools/convert.py
openerp/tools/translate.py
openerp/tools/yaml_import.py

diff --git a/.coveragerc b/.coveragerc
new file mode 100644 (file)
index 0000000..e4fb5ac
--- /dev/null
@@ -0,0 +1,19 @@
+# Config file .coveragerc
+# adapt the include for your project
+
+[report]
+include =
+    */OCA/OCB/addons/*
+    */OCA/OCB/openerp/*
+
+omit =
+    */tests/*
+    *__init__.py
+
+# Regexes for lines to exclude from consideration
+exclude_lines =
+    # Have to re-enable the standard pragma
+    pragma: no cover
+
+    # Don't complain about null context checking
+    if context is None:
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..6e3379a
--- /dev/null
@@ -0,0 +1,27 @@
+language: python
+
+python:
+  - "2.7"
+
+env:
+  - DATABASE="openerp_test"
+
+virtualenv:
+  system_site_packages: true
+
+install:
+  - git clone https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
+  - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
+  - sudo apt-get install python-lxml
+  - pip install QUnitSuite flake8 coveralls
+  - pip install -r ${HOME}/maintainer-quality-tools/travis/requirements.txt
+
+script:
+#  - travis_run_flake8
+  - createdb ${DATABASE}
+  - ./openerp-server -d ${DATABASE} --addons-path=./openerp/addons,./addons --stop-after-init --init=all
+  - coverage run ./openerp-server -d ${DATABASE} --addons-path=./openerp/addons,./addons --stop-after-init --init=all --test-enable --log-level=test | tee stdout.log
+  - if grep -v mail stdout.log | egrep -q "(At least one test failed when loading the modules.|ERROR ${DATABASE})"; then exit 1; fi
+
+after_success:
+  coveralls
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..e0be55c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+[![Build Status](https://travis-ci.org/OCA/OCB.png?branch=7.0)](https://travis-ci.org/OCA/OCB)
+[![Coverage Status](https://coveralls.io/repos/OCA/OCB/badge.png?branch=7.0)](https://coveralls.io/r/OCA/OCB?branch=master)
+
+[Original readme](README)
index c1aca3e..85a5dd1 100644 (file)
@@ -734,7 +734,7 @@ class account_journal(osv.osv):
         'entry_posted': fields.boolean('Skip \'Draft\' State for Manual Entries', help='Check this box if you don\'t want new journal entries to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation. \nNote that journal entries that are automatically created by the system are always skipping that state.'),
         'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
         'allow_date':fields.boolean('Check Date in Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'),
-
+        'active': fields.boolean('Active', select=True),
         'profit_account_id' : fields.many2one('account.account', 'Profit Account'),
         'loss_account_id' : fields.many2one('account.account', 'Loss Account'),
         'internal_account_id' : fields.many2one('account.account', 'Internal Transfers Account', select=1),
@@ -746,6 +746,7 @@ class account_journal(osv.osv):
         'with_last_closing_balance' : False,
         'user_id': lambda self, cr, uid, context: uid,
         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
+        'active': True,
     }
     _sql_constraints = [
         ('code_company_uniq', 'unique (code, company_id)', 'The code of the journal must be unique per company !'),
@@ -2717,6 +2718,11 @@ class account_tax_code_template(osv.osv):
         'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
         'sign': fields.float('Sign For Parent', required=True),
         'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any tax related to this tax Code to appear on invoices."),
+        'sequence': fields.integer(
+            'Sequence', help=(
+                "Determine the display order in the report 'Accounting "
+                "\ Reporting \ Generic Reporting \ Taxes \ Taxes Report'"),
+            ),
     }
 
     _defaults = {
@@ -2749,6 +2755,7 @@ class account_tax_code_template(osv.osv):
                 'parent_id': tax_code_template.parent_id and ((tax_code_template.parent_id.id in tax_code_template_ref) and tax_code_template_ref[tax_code_template.parent_id.id]) or False,
                 'company_id': company_id,
                 'sign': tax_code_template.sign,
+                'sequence': tax_code_template.sequence,
             }
             #check if this tax code already exists
             rec_list = obj_tax_code.search(cr, uid, [('name', '=', vals['name']),('code', '=', vals['code']),('company_id', '=', vals['company_id'])], context=context)
index e620359..740e7cd 100644 (file)
@@ -29,7 +29,7 @@ class account_analytic_line(osv.osv):
     _columns = {
         'product_uom_id': fields.many2one('product.uom', 'Unit of Measure'),
         'product_id': fields.many2one('product.product', 'Product'),
-        'general_account_id': fields.many2one('account.account', 'General Account', required=True, ondelete='restrict'),
+        'general_account_id': fields.many2one('account.account', 'General Account', ondelete='restrict'),
         'move_id': fields.many2one('account.move.line', 'Move Line', ondelete='cascade', select=True),
         'journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal', required=True, ondelete='restrict', select=True),
         'code': fields.char('Code', size=8),
index f90c966..f801901 100644 (file)
@@ -532,8 +532,8 @@ class account_invoice(osv.osv):
                 acc_id = p.property_account_payable.id
                 partner_payment_term = p.property_supplier_payment_term and p.property_supplier_payment_term.id or False
             fiscal_position = p.property_account_position and p.property_account_position.id or False
-            if p.bank_ids:
-                bank_id = p.bank_ids[0].id
+            if p.commercial_partner_id.bank_ids:
+                bank_id = p.commercial_partner_id.bank_ids[0].id
 
         result = {'value': {
             'account_id': acc_id,
@@ -1033,7 +1033,7 @@ class account_invoice(osv.osv):
             line = self.finalize_invoice_move_lines(cr, uid, inv, line)
 
             move = {
-                'ref': inv.reference and inv.reference or inv.name,
+                'ref': inv.reference or inv.supplier_invoice_number or inv.name,
                 'line_id': line,
                 'journal_id': journal_id,
                 'date': inv.date_invoice,
@@ -1122,6 +1122,23 @@ class account_invoice(osv.osv):
                         (ref, move_id))
         return True
 
+    def action_proforma(self, cr, uid, ids, context=None):
+        """
+        Check if all taxes are present with the correct base amount
+        on creating a proforma invoice. This leaves room for manual
+        corrections of the tax amount.
+        """
+        if not ids:
+            return True
+        if isinstance(ids, (int, long)):
+            ids = [ids]
+        ait_obj = self.pool.get('account.invoice.tax')
+        for inv in self.browse(cr, uid, ids, context=context):
+            compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context)
+            self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
+        return self.write(
+            cr, uid, ids, {'state': 'proforma2'}, context=context)
+
     def action_cancel(self, cr, uid, ids, context=None):
         if context is None:
             context = {}
@@ -1665,7 +1682,7 @@ class account_invoice_tax(osv.osv):
         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
         'name': fields.char('Tax Description', size=64, required=True),
         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
-        'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
+        'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account', readonly=True),
         'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
         'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
         'manual': fields.boolean('Manual'),
index f353877..018e486 100644 (file)
@@ -61,7 +61,7 @@
                         <group>
                             <field domain="[('company_id', '=', parent.company_id), ('journal_id', '=', parent.journal_id), ('type', '&lt;&gt;', 'view')]" name="account_id" on_change="onchange_account_id(product_id, parent.partner_id, parent.type, parent.fiscal_position,account_id)" groups="account.group_account_user"/>
                             <field name="invoice_line_tax_id" context="{'type':parent.get('type')}" domain="[('parent_id','=',False),('company_id', '=', parent.company_id)]" widget="many2many_tags"/>
-                            <field domain="[('type','&lt;&gt;','view'), ('company_id', '=', parent.company_id)], ('state','not in',('close','cancelled'))]" name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
+                            <field domain="[('type','&lt;&gt;','view'), ('company_id', '=', parent.company_id), ('state','not in',('close','cancelled'))]" name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
                             <field name="company_id" groups="base.group_multi_company" readonly="1"/>
                         </group>
                     </group>
                                 <field name="amount_untaxed" widget="monetary" options="{'currency_field': 'currency_id'}"/>
                                 <div>
                                     <label for="amount_tax"/>
-                                    <button name="button_reset_taxes" states="draft,proforma2"
+                                    <button name="button_reset_taxes" states="draft"
                                         string="(update)" class="oe_link oe_edit_only"
                                         type="object" help="Recompute taxes and total"/>
                                 </div>
                     <field name="sent" invisible="1"/>
                     <notebook colspan="4">
                         <page string="Invoice Lines">
-                            <field name="invoice_line" nolabel="1" widget="one2many_list" context="{'type': type}">
+                            <field name="invoice_line" nolabel="1" widget="one2many_list" context="{'type': type, 'company_id': company_id}">
                                 <tree string="Invoice Lines" editable="bottom">
                                     <field name="sequence" widget="handle"/>
                                     <field name="product_id"
                                 <field name="amount_untaxed" widget="monetary" options="{'currency_field': 'currency_id'}"/>
                                 <div>
                                     <label for="amount_tax"/>
-                                    <button name="button_reset_taxes" states="draft,proforma2"
+                                    <button name="button_reset_taxes" states="draft"
                                         string="(update)" class="oe_link oe_edit_only"
                                         type="object" help="Recompute taxes and total"/>
                                 </div>
index 13db193..607dd71 100644 (file)
@@ -17,7 +17,7 @@
         <record id="act_proforma2" model="workflow.activity">
             <field name="wkf_id" ref="wkf"/>
             <field name="name">proforma2</field>
-            <field name="action">write({'state':'proforma2'})</field>
+            <field name="action">action_proforma()</field>
             <field name="kind">function</field>
         </record>
 
index 9f85f58..065ad69 100644 (file)
@@ -557,8 +557,10 @@ class account_move_line(osv.osv):
     }
     _order = "date desc, id desc"
     _sql_constraints = [
-        ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in accounting entry !'),
-        ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'),
+        ('credit_debit1', 'CHECK ((credit * debit) = 0::numeric)',
+         'Wrong credit or debit value in accounting entry !'),
+        ('credit_debit2', 'CHECK ((credit + debit) >= 0::numeric)',
+         'Wrong credit or debit value in accounting entry !'),
     ]
 
     def _auto_init(self, cr, context=None):
index 2716e39..624831c 100644 (file)
             <field name="model">account.journal</field>
             <field name="arch" type="xml">
                 <form string="Account Journal" version="7.0">
+                    <sheet>
                     <div class="oe_title">
                         <label for="name" class="oe_edit_only"/>
                         <h1><field name="name"/></h1>
                             <field name="default_credit_account_id" attrs="{'required':[('type','in',('cash', 'bank'))]}" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
                             <field name="currency" groups="base.group_multi_currency"/>
                             <field name="company_id" groups="base.group_multi_company"/>
+                            <field name="active"/>
                         </group>
                     </group>
                     <notebook>
                             </field>
                         </page>
                     </notebook>
+                    </sheet>
                 </form>
             </field>
         </record>
                     <h1><field name="name"/></h1>
                     <group>
                         <group>
+                            <!-- last_closing_balance must be here because it is returned by onchange_journal_id -->
+                            <field name="last_closing_balance" invisible="1"/>
                             <field name="journal_id" domain="[('type', '=', 'bank')]" on_change="onchange_journal_id(journal_id)" widget="selection"/>
                             <label for="date" string="Date / Period"/>
                             <div>
index 8a450c0..65e0c01 100644 (file)
         Fiscal Periods
         -->    
         
+        <record id="period_0" model="account.period">
+            <field eval="'00/'+time.strftime('%Y')" name="code"/>
+            <field eval="'X 00/'+time.strftime('%Y')" name="name"/>
+            <field eval="True" name="special"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear"/>
+            <field eval="time.strftime('%Y')+'-01-01'" name="date_start"/>
+            <field eval="time.strftime('%Y')+'-01-01'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
         <record id="period_1" model="account.period">
             <field eval="'01/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 01/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-01-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-01-31'" name="date_stop"/>
@@ -30,7 +39,7 @@
         <record id="period_2" model="account.period">
             <field eval="'02/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 02/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-02-01'" name="date_start"/>
             <!-- for the last day of February, we have to compute the day before March 1st -->
@@ -40,7 +49,7 @@
         <record id="period_3" model="account.period">
             <field eval="'03/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 03/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-03-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-03-31'" name="date_stop"/>
@@ -49,7 +58,7 @@
         <record id="period_4" model="account.period">
             <field eval="'04/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 04/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-04-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-04-30'" name="date_stop"/>
@@ -58,7 +67,7 @@
         <record id="period_5" model="account.period">
             <field eval="'05/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 05/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-05-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-05-31'" name="date_stop"/>
@@ -68,7 +77,7 @@
             <field eval="'06/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 06/'+time.strftime('%Y')" name="name"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field eval="time.strftime('%Y')+'-06-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-06-30'" name="date_stop"/>
             <field name="company_id" ref="base.main_company"/>
@@ -76,7 +85,7 @@
         <record id="period_7" model="account.period">
             <field eval="'07/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 07/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-07-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-07-31'" name="date_stop"/>
@@ -85,7 +94,7 @@
         <record id="period_8" model="account.period">
             <field eval="'08/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 08/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-08-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-08-31'" name="date_stop"/>
         <record id="period_9" model="account.period">
             <field eval="'09/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 09/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-09-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-09-30'" name="date_stop"/>
         <record id="period_10" model="account.period">
             <field eval="'10/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 10/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-10-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-10-31'" name="date_stop"/>
         <record id="period_11" model="account.period">
             <field eval="'11/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 11/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-11-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-11-30'" name="date_stop"/>
         <record id="period_12" model="account.period">
             <field eval="'12/'+time.strftime('%Y')" name="code"/>
             <field eval="'X 12/'+time.strftime('%Y')" name="name"/>
-            <field eval="True" name="special"/>
+            <field eval="False" name="special"/>
             <field name="fiscalyear_id" ref="data_fiscalyear"/>
             <field eval="time.strftime('%Y')+'-12-01'" name="date_start"/>
             <field eval="time.strftime('%Y')+'-12-31'" name="date_stop"/>
index b7c2612..110b921 100644 (file)
@@ -57,6 +57,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
             self.ACCOUNT_TYPE = ['payable']
         else:
             self.ACCOUNT_TYPE = ['payable','receivable']
+        self.partner_ids = ids if data.get('model') == 'res.partner' else False
         return super(aged_trial_report, self).set_context(objects, data, ids, report_type=report_type)
 
     def _get_lines(self, form):
@@ -64,6 +65,15 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
         move_state = ['draft','posted']
         if self.target_move == 'posted':
             move_state = ['posted']
+        query_params = [
+            tuple(move_state), tuple(self.ACCOUNT_TYPE),
+            self.date_from, self.date_from]
+        if self.partner_ids:
+            partner_query = ' AND res_partner.id IN %s '
+            query_params.append(tuple(self.partner_ids))
+        else:
+            partner_query = ''
+
         self.cr.execute('SELECT DISTINCT res_partner.id AS id,\
                     res_partner.name AS name \
                 FROM res_partner,account_move_line AS l, account_account, account_move am\
@@ -73,11 +83,11 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
                     AND (account_account.type IN %s)\
                     AND account_account.active\
                     AND ((reconcile_id IS NULL)\
-                       OR (reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date > %s )))\
+                       OR (reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date >= %s::timestamp + \'1day\'::interval )))\
                     AND (l.partner_id=res_partner.id)\
                     AND (l.date <= %s)\
-                    AND ' + self.query + ' \
-                ORDER BY res_partner.name', (tuple(move_state), tuple(self.ACCOUNT_TYPE), self.date_from, self.date_from,))
+                    AND ' + self.query + partner_query + ' \
+                ORDER BY res_partner.name', tuple(query_params))
         partners = self.cr.dictfetchall()
         ## mise a 0 du total
         for i in range(7):
@@ -97,7 +107,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
                     AND (account_account.type IN %s)\
                     AND (l.partner_id IN %s)\
                     AND ((l.reconcile_id IS NULL)\
-                    OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date > %s )))\
+                    OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date >= %s::timestamp + \'1day\'::interval )))\
                     AND ' + self.query + '\
                     AND account_account.active\
                     AND (l.date <= %s)\
@@ -117,7 +127,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
                         AND (COALESCE(l.date_maturity, l.date) < %s)\
                         AND (l.partner_id IN %s)\
                         AND ((l.reconcile_id IS NULL)\
-                        OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date > %s )))\
+                        OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date >= %s::timestamp + \'1day\'::interval )))\
                         AND '+ self.query + '\
                         AND account_account.active\
                     AND (l.date <= %s)\
@@ -134,7 +144,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
                         AND (COALESCE(l.date_maturity,l.date) > %s)\
                         AND (l.partner_id IN %s)\
                         AND ((l.reconcile_id IS NULL)\
-                        OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date > %s )))\
+                        OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date >= %s::timestamp + \'1day\'::interval )))\
                         AND '+ self.query + '\
                         AND account_account.active\
                     AND (l.date <= %s)\
@@ -166,7 +176,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
                         AND (account_account.type IN %s)
                         AND (l.partner_id IN %s)
                         AND ((l.reconcile_id IS NULL)
-                          OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date > %s )))
+                          OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date >= %s::timestamp + \'1day\'::interval )))
                         AND ''' + self.query + '''
                         AND account_account.active
                         AND ''' + dates_query + '''
@@ -236,6 +246,8 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
         return res
 
     def _get_lines_with_out_partner(self, form):
+        if self.partner_ids:
+            return []
         res = []
         move_state = ['draft','posted']
         if self.target_move == 'posted':
@@ -252,7 +264,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
                     AND (l.partner_id IS NULL)\
                     AND (account_account.type IN %s)\
                     AND ((l.reconcile_id IS NULL) \
-                    OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date > %s )))\
+                    OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date >= %s::timestamp + \'1day\'::interval )))\
                     AND ' + self.query + '\
                     AND (l.date <= %s)\
                     AND account_account.active ',(tuple(move_state), tuple(self.ACCOUNT_TYPE), self.date_from, self.date_from,))
@@ -269,7 +281,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
                         AND (account_account.type IN %s)\
                         AND (COALESCE(l.date_maturity, l.date) < %s)\
                         AND ((l.reconcile_id IS NULL)\
-                        OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date > %s )))\
+                        OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date >= %s::timestamp + \'1day\'::interval )))\
                         AND '+ self.query + '\
                         AND account_account.active ', (tuple(move_state), tuple(self.ACCOUNT_TYPE), self.date_from, self.date_from))
             t = self.cr.fetchall()
@@ -284,7 +296,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
                         AND (account_account.type IN %s)\
                         AND (COALESCE(l.date_maturity,l.date) > %s)\
                         AND ((l.reconcile_id IS NULL)\
-                        OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date > %s )))\
+                        OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date >= %s::timestamp + \'1day\'::interval )))\
                         AND '+ self.query + '\
                         AND account_account.active ', (tuple(move_state), tuple(self.ACCOUNT_TYPE), self.date_from, self.date_from))
             t = self.cr.fetchall()
@@ -312,7 +324,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
                         AND (account_account.type IN %s)\
                         AND (l.partner_id IS NULL)\
                         AND ((l.reconcile_id IS NULL)\
-                        OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date > %s )))\
+                        OR (l.reconcile_id IN (SELECT recon.id FROM account_move_reconcile AS recon WHERE recon.create_date >= %s::timestamp + \'1day\'::interval )))\
                         AND '+ self.query + '\
                         AND account_account.active\
                         AND ' + dates_query + '\
index b3cbcb4..2cdfbdd 100644 (file)
@@ -59,16 +59,17 @@ class tax_report(report_sxw.rml_parse, common_report_header):
             'get_start_period': self.get_start_period,
             'get_end_period': self.get_end_period,
             'get_basedon': self._get_basedon,
+            'get_target_move': self._get_target_move,
         })
 
     def _get_basedon(self, form):
         return form['form']['based_on']
 
-    def _get_lines(self, based_on, company_id=False, parent=False, level=0, context=None):
+    def _get_lines(self, based_on, target_move, company_id=False, parent=False, level=0, context=None):
         period_list = self.period_ids
         res = self._get_codes(based_on, company_id, parent, level, period_list, context=context)
         if period_list:
-            res = self._add_codes(based_on, res, period_list, context=context)
+            res = self._add_codes(based_on, target_move, res, period_list, context=context)
         else:
             self.cr.execute ("select id from account_fiscalyear")
             fy = self.cr.fetchall()
@@ -76,7 +77,7 @@ class tax_report(report_sxw.rml_parse, common_report_header):
             periods = self.cr.fetchall()
             for p in periods:
                 period_list.append(p[0])
-            res = self._add_codes(based_on, res, period_list, context=context)
+            res = self._add_codes(based_on, target_move, res, period_list, context=context)
 
         i = 0
         top_result = []
@@ -171,18 +172,26 @@ class tax_report(report_sxw.rml_parse, common_report_header):
             res += self._get_codes(based_on, company_id, code.id, level+1, context=context)
         return res
 
-    def _add_codes(self, based_on, account_list=None, period_list=None, context=None):
+    def _add_codes(self, based_on, target_move, account_list=None, period_list=None, context=None):
         if account_list is None:
             account_list = []
         if period_list is None:
             period_list = []
+        if context is None:
+            context = {}
         res = []
         obj_tc = self.pool.get('account.tax.code')
         for account in account_list:
             ids = obj_tc.search(self.cr, self.uid, [('id','=', account[1].id)], context=context)
             sum_tax_add = 0
-            for period_ind in period_list:
-                for code in obj_tc.browse(self.cr, self.uid, ids, {'period_id':period_ind,'based_on': based_on}):
+            for period_id in period_list:
+                ctx = context.copy()
+                ctx.update(
+                        period_id=period_id,
+                        based_on=based_on,
+                        state=target_move,
+                        )
+                for code in obj_tc.browse(self.cr, self.uid, ids, context=ctx):
                     sum_tax_add = sum_tax_add + code.sum_period
 
             code.sum_period = sum_tax_add
index c25f1fc..f74c668 100644 (file)
                                </tr>
                        </blockTable>
                </td>
-               <td><para style="terp_default_Centre_8">[[ get_basedon(data)  or '' ]]</para></td>
+               <td>
+                   <para style="terp_default_Centre_8">[[ get_basedon(data)  or '' ]]</para>
+                   <para style="terp_default_Centre_8">[[ get_target_move(data) or '' ]]</para>
+               </td>
    </tr>
   </blockTable>
   <para style="P2"><font color="white"> </font></para>
            <td><para style="P12a">Tax Amount</para></td>
          </tr>
          <tr>
-              <td><para style="P5"><font>[[ repeatIn(get_lines(data['form']['based_on'], data['form']['company_id']), 'o') ]]</font><font color="white">[[ (o['level']) ]]</font> <font>[[ (len(o['level'])&lt;5 and setTag('para','para',{'fontName':'Helvetica-Bold'})) or removeParentNode('font') ]]</font><font>[[ o['code'] ]]  [[ o['name'] ]] </font></para></td>
+              <td><para style="P5"><font>[[ repeatIn(get_lines(data['form']['based_on'], data['form']['target_move'],data['form']['company_id']), 'o') ]]</font><font color="white">[[ (o['level']) ]]</font> <font>[[ (len(o['level'])&lt;5 and setTag('para','para',{'fontName':'Helvetica-Bold'})) or removeParentNode('font') ]]</font><font>[[ o['code'] ]]  [[ o['name'] ]] </font></para></td>
            <td><para style="P6"><font>[[ len(o['level'])&lt;5 and setTag('para','para',{'fontName':"Helvetica-Bold"})  or removeParentNode('font')]]</font><font>[[ o['type']=='view' and removeParentNode('font') ]][[ formatLang(o['debit']) ]]</font><font>[[ o['type']&lt;&gt;'view' and removeParentNode('font') ]][[ formatLang(o['debit']) ]]</font></para></td>
            <td><para style="P6"><font>[[ len(o['level'])&lt;5 and setTag('para','para',{'fontName':"Helvetica-Bold"})  or removeParentNode('font')]]</font><font>[[ o['type']=='view' and removeParentNode('font') ]][[ formatLang(o['credit']) ]]</font><font>[[ o['type']&lt;&gt;'view' and removeParentNode('font') ]][[ formatLang(o['credit'])]]</font></para></td>
            <td><para style="P6"><font>[[ len(o['level'])&lt;5 and setTag('para','para',{'fontName':"Helvetica-Bold"})  or removeParentNode('font')]]</font><font>[[ o['type']=='view' and removeParentNode('font') ]][[ formatLang(o['tax_amount'], currency_obj=company.currency_id) ]]</font><font>[[ o['type']&lt;&gt;'view' and removeParentNode('font') ]][[ formatLang(o['tax_amount'], currency_obj=company.currency_id) ]]</font> </para></td>
index 1054e7f..aa83a59 100644 (file)
@@ -49,6 +49,8 @@ class account_aged_trial_balance(osv.osv_memory):
             context = {}
 
         data = self.pre_print_report(cr, uid, ids, data, context=context)
+        data['ids'] = context.get('active_ids', [])
+        data['model'] = context.get('active_model', 'ir.ui.menu')
         data['form'].update(self.read(cr, uid, ids, ['period_length', 'direction_selection'])[0])
 
         period_length = data['form']['period_length']
@@ -78,8 +80,6 @@ class account_aged_trial_balance(osv.osv_memory):
                 }
                 start = stop + relativedelta(days=1)
         data['form'].update(res)
-        if data.get('form',False):
-            data['ids']=[data['form'].get('chart_account_id',False)]
         return {
             'type': 'ir.actions.report.xml',
             'report_name': 'account.aged_trial_balance',
index be1d710..556d590 100644 (file)
              <field name="target">new</field>
        </record>
 
+       <record model="ir.values" id="ir_values_account_aged_partner_balance">
+           <field name="key2" eval="'client_print_multi'"/>
+           <field name="model" eval="'res.partner'"/>
+           <field name="name">Print Aged Partner Balance</field>
+           <field name="value" eval="'ir.actions.act_window,%d'%action_account_aged_balance_view"/>
+       </record>
+
         <menuitem icon="STOCK_PRINT"
             name="Aged Partner Balance"
             action="action_account_aged_balance_view"
index bbef144..b6d59b0 100644 (file)
@@ -136,7 +136,11 @@ class account_common_report(osv.osv_memory):
         return fiscalyears and fiscalyears[0] or False
 
     def _get_all_journal(self, cr, uid, context=None):
-        return self.pool.get('account.journal').search(cr, uid ,[])
+        # The 'active_test' context is used, because reports must
+        # return entries created on inactive journals.
+        return self.pool['account.journal'].search(
+            cr, uid, [], context=dict(context or {}, active_test=False)
+        )
 
     _defaults = {
             'fiscalyear_id': _get_fiscalyear,
@@ -174,7 +178,17 @@ class account_common_report(osv.osv_memory):
         data = {}
         data['ids'] = context.get('active_ids', [])
         data['model'] = context.get('active_model', 'ir.ui.menu')
-        data['form'] = self.read(cr, uid, ids, ['date_from',  'date_to',  'fiscalyear_id', 'journal_ids', 'period_from', 'period_to',  'filter',  'chart_account_id', 'target_move'], context=context)[0]
+        data['form'] = self.read(
+            cr, uid, ids,
+            [
+                'date_from', 'date_to', 'fiscalyear_id', 'period_from',
+                'period_to', 'filter', 'chart_account_id', 'target_move'
+            ], context=context)[0]
+        # The 'active_test' context is used, because reports must
+        # return entries created on inactive journals.
+        data['form']['journal_ids'] = self.read(
+            cr, uid, ids, ['journal_ids'], context=dict(context or {}, active_test=False)
+        )[0]['journal_ids']
         for field in ['fiscalyear_id', 'chart_account_id', 'period_from', 'period_to']:
             if isinstance(data['form'][field], tuple):
                 data['form'][field] = data['form'][field][0]
index 8366791..2e0a231 100644 (file)
@@ -12,6 +12,7 @@
                     <field name="chart_tax_id" widget='selection'/>
                     <field name="fiscalyear_id"/>
                     <field name="display_detail"/>
+                    <field name="target_move"/>
                     <!--- <field name="based_on"/>--> <!-- the option based_on 'payment' is probably not fully compliant with what the users understand with that term. So, currently, it's seems better to remove it from the view to avoid further problems -->
                 </group>
 
index 1257071..550481d 100644 (file)
                         <group>
                             <field name="pricelist_id"
                                 class="oe_inline"
-                                attrs="{'required': [('invoice_on_timesheets', '=', True)]}"/>
+                                attrs="{'required': ['|',('type','=','template'),('type','=','contract'), ('invoice_on_timesheets', '=', True)]}"/>
                             <field name="to_invoice"
                                 class="oe_inline"
                                 widget="selection"
-                                attrs="{'required': [('invoice_on_timesheets', '=', True)]}"/>
+                                attrs="{'required': ['|',('type','=','template'),('type','=','contract'), ('invoice_on_timesheets', '=', True)]}"/>
                         </group>
                     </group>
                  </xpath>
index 9cf4c14..ed7a456 100644 (file)
@@ -181,7 +181,7 @@ class account_asset_asset(osv.osv):
                      'amount': amount,
                      'asset_id': asset.id,
                      'sequence': i,
-                     'name': str(asset.id) +'/' + str(i),
+                     'name': "%s/%s" %(i, undone_dotation_number),
                      'remaining_value': residual_amount,
                      'depreciated_value': (asset.purchase_value - asset.salvage_value) - (residual_amount + amount),
                      'depreciation_date': depreciation_date.strftime('%Y-%m-%d'),
@@ -410,9 +410,8 @@ class account_asset_depreciation_line(osv.osv):
             asset_name = line.asset_id.name
             reference = line.name
             move_vals = {
-                'name': asset_name,
                 'date': depreciation_date,
-                'ref': reference,
+                'ref': "%s %s" %(line.asset_id.code or line.asset_id.name, line.name),
                 'period_id': period_ids and period_ids[0] or False,
                 'journal_id': line.asset_id.category_id.journal_id.id,
                 }
index ac79ca1..2f13b2a 100644 (file)
@@ -114,14 +114,30 @@ class crossovered_budget_lines(osv.osv):
         result = 0.0
         if context is None: 
             context = {}
+        account_obj = self.pool.get('account.account')
         for line in self.browse(cr, uid, ids, context=context):
             acc_ids = [x.id for x in line.general_budget_id.account_ids]
             if not acc_ids:
                 raise osv.except_osv(_('Error!'),_("The Budget '%s' has no accounts!") % ustr(line.general_budget_id.name))
+            acc_ids = account_obj._get_children_and_consol(cr, uid, acc_ids, context=context)
             date_to = line.date_to
             date_from = line.date_from
             if line.analytic_account_id.id:
-                cr.execute("SELECT SUM(amount) FROM account_analytic_line WHERE account_id=%s AND (date "
+                cr.execute("SELECT SUM(amount) FROM account_analytic_line WHERE account_id in "
+                       """(with recursive account_analytic_account_hierarchy(id)
+                        as 
+                            (
+                                select id from account_analytic_account 
+                                    where id=%s
+                                union all
+                                select account_analytic_account.id from 
+                                    account_analytic_account 
+                                    join account_analytic_account_hierarchy
+                                    on account_analytic_account.parent_id=
+                                        account_analytic_account_hierarchy.id
+                            )"""
+                       "select id from account_analytic_account_hierarchy) "
+                       "AND (date "
                        "between to_date(%s,'yyyy-mm-dd') AND to_date(%s,'yyyy-mm-dd')) AND "
                        "general_account_id=ANY(%s)", (line.analytic_account_id.id, date_from, date_to,acc_ids,))
                 result = cr.fetchone()[0]
index 89840b0..355973c 100644 (file)
                     </div>
                     <group>
                         <group>
+                            <field name="total"/>
                             <field name="user_id"/>
                             <field name="mode"/>
                         </group>
                                         <field name="move_line_id" on_change="onchange_move_line(move_line_id,parent.mode,parent.date_prefered,parent.date_scheduled,currency,company_currency)" domain="[('reconcile_id','=', False), ('credit', '>',0),('amount_to_pay','>',0)] "/>
                                         <separator colspan="4" string="Transaction Information"/>
                                         <field name="date"/>
-                                        <label for="amount_currency" groups="base.group_multi_currency"/>
-                                        <div groups="base.group_multi_currency">
+                                        <label for="amount_currency"/>
+                                        <div>
                                             <field name="amount_currency" on_change="onchange_amount(amount_currency,currency,company_currency)" class="oe_inline"/>
-                                            <field name="currency" nolabel="1" class="oe_inline"/>
+                                            <field name="currency" nolabel="1" class="oe_inline" groups="base.group_multi_currency"/>
                                         </div>
                                         <field name="partner_id" on_change="onchange_partner(partner_id,parent.mode)"/>
                                         <field domain="[('partner_id','=',partner_id)]" name="bank_id"/>
                             <field name="bank_id" domain="[('partner_id', '=', partner_id)]"/>
                             <field name="ml_maturity_date"/>
                             <field name="date"/>
-                            <field name="amount_currency" string="Amount" groups="base.group_multi_currency"/>
+                            <field name="amount_currency" string="Amount"/>
                             <field name="currency" groups="base.group_multi_currency"/>
                             <field name="name"/>
                             <field name="amount" sum="Total in Company Currency" invisible="1"/>
index 230cb75..ba79943 100644 (file)
@@ -104,7 +104,10 @@ class payment_order_create(osv.osv_memory):
 #        payment = self.pool.get('payment.order').browse(cr, uid, context['active_id'], context=context)
 
         # Search for move line to pay:
-        domain = [('reconcile_id', '=', False), ('account_id.type', '=', 'payable'), ('amount_to_pay', '>', 0)]
+        domain = [('reconcile_id', '=', False),
+                  ('account_id.type', '=', 'payable'),
+                  ('journal_id.type','<>','bank'),
+                  ('amount_to_pay', '>', 0)]
         domain = domain + ['|', ('date_maturity', '<=', search_due_date), ('date_maturity', '=', False)]
         line_ids = line_obj.search(cr, uid, domain, context=context)
         context.update({'line_ids': line_ids})
index 0faca84..28ecddc 100644 (file)
@@ -29,7 +29,7 @@
                                 <field name="type" invisible="context.get('default_type', False)"/>
                                 <field name="template_id" on_change="on_change_template(template_id,context)" domain="[('type','=','template')]" attrs="{'invisible': [('type','in',['view', 'normal','template'])]}" context="{'default_type' : 'template'}"/>
                                 <field name="code"/>
-                                <field name="parent_id" on_change="on_change_parent(parent_id)" attrs="{'invisible': [('type','in',['contract'])]}"/>
+                                <field name="parent_id" on_change="on_change_parent(parent_id)"/>
                                 <field name="company_id" on_change="on_change_company(company_id)" widget="selection" groups="base.group_multi_company" attrs="{'required': [('type','&lt;&gt;','view')]}"/>
                             </group>
                         </group>
index 6f2d69e..8eab548 100644 (file)
                     <attribute name="attrs">{'invisible': [('invoice_on_timesheets','=',False),('charge_expenses','=',False)]}</attribute>
                 </xpath>
                 <xpath expr="//field[@name='pricelist_id']" position="attributes">
-                    <attribute name="attrs">{'required': ['|',('invoice_on_timesheets','=',True),('charge_expenses','=',True)]}</attribute>
+                    <attribute name="attrs">{'required': ['|',('type','=','template'),('type','=','contract'), '|', ('invoice_on_timesheets','=',True),('charge_expenses','=',True)]}</attribute>
                 </xpath>
                 <xpath expr="//field[@name='to_invoice']" position="attributes">
-                    <attribute name="attrs">{'required': ['|',('invoice_on_timesheets','=',True),('charge_expenses','=',True)]}</attribute>
+                    <attribute name="attrs">{'required': ['|',('type','=','template'),('type','=','contract'), '|', ('invoice_on_timesheets','=',True),('charge_expenses','=',True)]}</attribute>
                     <attribute name="string">Expenses and Timesheet Invoicing Ratio</attribute>
                 </xpath>
             </field>
index 69e1add..457d30d 100644 (file)
@@ -65,8 +65,8 @@
             <field name="priority" eval="18"/>
             <field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form"/>
             <field name="arch" type="xml">
-                <xpath expr="//field[@name='timesheet_ids']/tree/field[@name='account_id']" position="replace">
-                    <field name="account_id" domain="[('type','=','normal'),('state', '&lt;&gt;', 'close')]" on_change="on_change_account_id(account_id, user_id, unit_amount)"/>
+                <xpath expr="//field[@name='timesheet_ids']/tree/field[@name='account_id']" position="attributes">
+                    <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
                 </xpath>
             </field>
         </record>
@@ -78,8 +78,8 @@
             <field name="priority" eval="19"/>
             <field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form"/>
             <field name="arch" type="xml">
-                <xpath expr="//field[@name='timesheet_ids']/form/field[@name='account_id']" position="replace">
-                    <field name="account_id" domain="[('type','=','normal'),('state', '&lt;&gt;', 'close')]" on_change="on_change_account_id(account_id, user_id, unit_amount)"/>
+                <xpath expr="//field[@name='timesheet_ids']/form/field[@name='account_id']" position="attributes">
+                    <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
                 </xpath>
             </field>
         </record>
@@ -90,8 +90,8 @@
             <field name="model">hr.analytic.timesheet</field>
             <field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
             <field name="arch" type="xml">
-                <xpath expr="//field[@name='account_id']" position="replace">
-                    <field name="account_id" domain="[('type','=','normal'),('state', '&lt;&gt;', 'close')]" on_change="on_change_account_id(account_id, user_id, unit_amount)" />
+                <xpath expr="//field[@name='account_id']" position="attributes">
+                    <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
                 </xpath>
             </field>
         </record>
             <field name="model">hr.analytic.timesheet</field>
             <field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
             <field name="arch" type="xml">
-                <xpath expr="/tree/field[@name='account_id']" position="replace">
-                    <field name="account_id" domain="[('type','=','normal'),('state', '&lt;&gt;', 'close')]" on_change="on_change_account_id(account_id, user_id, unit_amount)" />
+                <xpath expr="/tree/field[@name='account_id']" position="attributes">
+                    <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
                 </xpath>
             </field>
         </record>
index 0f7ef0c..b51e960 100644 (file)
@@ -88,7 +88,10 @@ def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
                 return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S"))
             return int(real_id)
 
-    return base_calendar_id and int(base_calendar_id) or base_calendar_id
+        return int(base_calendar_id)
+
+    return base_calendar_id
+    
 
 def get_real_ids(ids):
     if isinstance(ids, (str, int, long)):
@@ -1134,7 +1137,11 @@ rule or repeating pattern of time to exclude from the recurring rule."),
                     'email': partner.email
                 }, context=local_context)
                 if partner.email:
-                    mail_to = mail_to + " " + partner.email
+                    mail_to = (
+                        mail_to + ";" + partner.email
+                        if mail_to
+                        else partner.email
+                    )
                 self.write(cr, uid, [event.id], {
                     'attendee_ids': [(4, att_id)]
                 }, context=context)
index e0311d1..9719c1e 100644 (file)
                 name="Phone calls"
                 groups="base.group_sale_salesman"
                 res_model="crm.phonecall"
+                src_model="crm.lead"
                 view_mode="tree,calendar,form"
                 context="{'default_duration': 1.0 ,'default_opportunity_id': active_id}"
+                domain="[('opportunity_id', '=', active_id)]"
                 view_type="form"/>
 
         <act_window
                 id="act_crm_opportunity_crm_meeting_new"
                 name="Meetings"
                 res_model="crm.meeting"
+                src_model="crm.lead"
                 view_mode="tree,form,calendar"
                 context="{'default_duration': 4.0, 'default_opportunity_id': active_id}"
+                domain="[('opportunity_id', '=', active_id)]"
                 view_type="form"/>
 
 
index c4b014b..dada85e 100644 (file)
@@ -61,6 +61,7 @@ class crm_phonecall_report(osv.osv):
                         domain="[('section_id','=',section_id),\
                         ('object_id.model', '=', 'crm.phonecall')]"),
         'partner_id': fields.many2one('res.partner', 'Partner' , readonly=True),
+        'opportunity_id': fields.many2one('crm.lead', 'Lead/Opportunity' , readonly=True),
         'company_id': fields.many2one('res.company', 'Company', readonly=True),
         'opening_date': fields.date('Opening Date', readonly=True, select=True),
         'creation_date': fields.date('Creation Date', readonly=True, select=True),
@@ -88,6 +89,7 @@ class crm_phonecall_report(osv.osv):
                     c.section_id,
                     c.categ_id,
                     c.partner_id,
+                    c.opportunity_id,
                     c.duration,
                     c.company_id,
                     c.priority,
index 06966f3..1b2bc77 100644 (file)
@@ -16,6 +16,7 @@
                     <field name="user_id" invisible="1"/>
                     <field name="company_id" invisible="1"/>
                     <field name="partner_id" invisible="1"/>
+                    <field name="opportunity_id" invisible="1"/>
                     <field name="state" invisible="1"/>
                     <field name="categ_id" invisible="1"/>
                     <field name="day" invisible="1"/>
@@ -72,6 +73,7 @@
                         <filter string="Salesperson" name="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}" />
                         <filter string="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}" />
                         <filter string="Partner" icon="terp-partner" context="{'group_by':'partner_id'}" />
+                        <filter string="Lead/Opportunity" icon="terp-personal+" context="{'group_by':'opportunity_id'}" />
                         <filter string="Priority"  icon="terp-rating-rated" domain="[]" context="{'group_by':'priority'}" />
                         <filter string="Category" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'categ_id'}" />
                         <filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}" />
index dffc156..fd40d41 100644 (file)
@@ -43,6 +43,7 @@ invoices from picking, OpenERP is able to add and compute the shipping line.
     'demo': ['delivery_demo.xml'],
     'test': ['test/delivery_cost.yml',
              'test/delivery_chained_pickings.yml',
+             'test/delivery_tracking_ref_backorder.yml',
             ],
     'installable': True,
     'auto_install': False,
index 5a3750e..fe37e54 100644 (file)
     </blockTable>
   </pto_header>
     <para style="terp_default_9">[[repeatIn(objects,'o')]]</para>
+    <para style="terp_default_8">[[ setLang(o.partner_id.lang) ]]</para>
     <para style="terp_default_8">
       <font color="white"> </font>
     </para>
index 263a476..3db21a8 100644 (file)
@@ -51,6 +51,12 @@ class stock_picking(osv.osv):
             result[line.picking_id.id] = True
         return result.keys()
 
+    def write(self, cr, uid, ids, vals, context=None):
+        if context is None: context = {}
+        if vals.get('backorder_id'):
+            vals.update(carrier_tracking_ref = '')
+        return super(stock_picking, self).write(cr, uid, ids, vals, context=context)
+
     _columns = {
         'carrier_id':fields.many2one("delivery.carrier","Carrier"),
         'volume': fields.float('Volume'),
diff --git a/addons/delivery/test/delivery_tracking_ref_backorder.yml b/addons/delivery/test/delivery_tracking_ref_backorder.yml
new file mode 100644 (file)
index 0000000..8cba6c4
--- /dev/null
@@ -0,0 +1,41 @@
+-
+  In order to partial delivery with tracking number
+-
+  I create an outgoing picking
+-
+ !record {model: stock.picking, id: outgoing_shipment}:
+    type: out
+    carrier_tracking_ref: ABC12345
+    volume: 2
+    number_of_packages: 2
+-
+ !record {model: stock.move, id: outgoing_move}:
+    picking_id: outgoing_shipment
+    product_id: product.product_product_8
+    product_uom: product.product_uom_unit
+    product_qty: 2
+    location_id: stock.stock_location_stock
+    location_dest_id: stock.stock_location_customers
+-
+ I confirm the picking
+-
+  !workflow {model: stock.picking, action: button_confirm, ref: outgoing_shipment}
+-
+ I make a partial delivery
+-
+  !python {model: stock.partial.picking}: |
+    pick_ids = [ref("outgoing_shipment")]
+    partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': pick_ids})
+    partial = self.browse(cr, uid, partial_id, context=context)
+    partial.move_ids[0].write({'quantity': 1})
+    self.do_partial(cr, uid, [partial_id])
+-
+  I check backorder shipment after partial delivery.
+-
+  !python {model: stock.picking}: |
+    shipment = self.browse(cr, uid, ref("delivery.outgoing_shipment"))
+    backorder = shipment.backorder_id
+    assert backorder, "Backorder should be created after partial shipment."
+    assert backorder.state == 'done', "Backorder should be close after received"
+    assert backorder.carrier_tracking_ref ==  'ABC12345', 'wrong tracking ref on done picking'
+    assert not shipment.carrier_tracking_ref, 'remaining picking must have no tracking ref'
index c5e24b0..0a08e71 100644 (file)
@@ -342,7 +342,8 @@ class email_template(osv.osv):
             service = netsvc.LocalService(report_service)
             (result, format) = service.create(cr, uid, [res_id], {'model': template.model}, ctx)
             # TODO in trunk, change return format to binary to match message_post expected format
-            result = base64.b64encode(result)
+            if not ctx.get('default_composition_mode', False) == 'mass_mail':
+                result = base64.b64encode(result)
             if not report_name:
                 report_name = report_service
             ext = "." + format
index e14e79a..2eb0e2a 100644 (file)
@@ -225,6 +225,24 @@ class hr_expense_expense(osv.osv):
             total_currency -= i['amount_currency'] or i['price']
         return total, total_currency, account_move_lines
 
+    def action_expense_cancel(self, cr, uid, ids, context=None):
+        ## We will first check the the move is not reconciled
+        for expense in self.browse(cr, uid, ids, context=context):
+            if expense.account_move_id:
+                for move_line in expense.account_move_id.line_id:
+                    if move_line.reconcile_id or move_line.reconcile_partial_id:
+                         raise osv.except_osv(
+                                 _('Error!'),
+                                 _('Please unreconcile payment accounting entries before cancelling this expense'))
+                ### Then we unlink the move line
+                self.pool.get('account.move').unlink(cr, uid, [expense.account_move_id.id], context=context)
+            wf_service = netsvc.LocalService("workflow")
+            wf_service.trg_delete(uid, 'hr.expense.expense', expense.id, cr)
+            wf_service.trg_create(uid, 'hr.expense.expense', expense.id, cr)
+            wf_service.trg_validate(uid, 'hr.expense.expense', expense.id, 'draft', cr)
+
+        return True
+
 
     def action_receipt_create(self, cr, uid, ids, context=None):
         '''
index 0d66784..d8b7aa3 100644 (file)
@@ -67,6 +67,7 @@
                     <button name="refuse" states="confirm,accepted" string="Refuse" type="workflow" groups="base.group_hr_user" />
                     <button name="draft" states="confirm,cancelled" string="Set to Draft" type="workflow" groups="base.group_hr_user" />
                     <button name="done" states="accepted" string="Generate Accounting Entries" type="workflow" groups="account.group_account_invoice" class="oe_highlight"/>
+                    <button name="action_expense_cancel" states="done,paid" string="Cancel Expense" type="object" />
                     <button name="action_view_receipt" states="done" string="Open Accounting Entries" type="object" groups="account.group_account_invoice"/>
                     <field name="state" widget="statusbar" statusbar_visible="draft,confirm,accepted,done,paid" statusbar_colors='{"confirm":"blue","cancelled":"red"}'/>
                 </header>
index 3813014..42d3719 100644 (file)
@@ -323,6 +323,7 @@ class hr_holidays(osv.osv):
             'state': 'draft',
             'manager_id': False,
             'manager_id2': False,
+            'number_of_days_temp': 0,
         })
         wf_service = netsvc.LocalService("workflow")
         for id in ids:
index 0392cb4..c5057b9 100644 (file)
@@ -70,10 +70,31 @@ class hr_analytic_timesheet(osv.osv):
     _table = 'hr_analytic_timesheet'
     _description = "Timesheet Line"
     _inherits = {'account.analytic.line': 'line_id'}
-    _order = "id desc"
+    _order = "date_aal DESC, account_name ASC"
+
+    def _get_account_analytic_line(self, cr, uid, ids, context=None):
+        ts_line_ids = self.pool.get('hr.analytic.timesheet').search(cr, uid, [('line_id', 'in', ids)], context=context)
+        return ts_line_ids
+
+    def _get_account_analytic_account(self, cr, uid, ids, context=None):
+        ts_line_ids = self.pool.get('hr.analytic.timesheet').search(cr, uid, [('account_id', 'in', ids)], context=context)
+        return ts_line_ids
+
     _columns = {
         'line_id': fields.many2one('account.analytic.line', 'Analytic Line', ondelete='cascade', required=True),
         'partner_id': fields.related('account_id', 'partner_id', type='many2one', string='Partner', relation='res.partner', store=True),
+
+        'date_aal': fields.related('line_id', 'date', string="Analytic Line Date", type='date',
+            store={
+                'account.analytic.line': (_get_account_analytic_line, ['date'], 10),
+                'hr.analytic.timesheet': (lambda self,cr,uid,ids,context=None: ids, None, 10),
+                }),
+        'account_name': fields.related('account_id', 'name', string="Analytic Account Name", type='char', size=256,
+            store={
+                'account.analytic.account': (_get_account_analytic_account, ['name'], 10),
+                'hr.analytic.timesheet': (lambda self,cr,uid,ids,context=None: ids, None, 10),
+                }
+            ),
     }
 
     def unlink(self, cr, uid, ids, context=None):
@@ -182,6 +203,9 @@ class hr_analytic_timesheet(osv.osv):
         ename = ''
         if emp_id:
             ename = emp_obj.browse(cr, uid, emp_id[0], context=context).name
+        res = self.on_change_unit_amount(cr, uid, id, vals.get('product_id'), vals.get('unit_amount'), False, False, vals.get('journal_id'), context)
+        if res['value'].get('amount'):
+            vals.update(amount=res['value']['amount'])
         if not vals.get('journal_id',False):
            raise osv.except_osv(_('Warning!'), _('No \'Analytic Journal\' is defined for employee %s \nDefine an employee for the selected user and assign an \'Analytic Journal\'!')%(ename,))
         if not vals.get('account_id',False):
index 21da4f2..d26dd8f 100644 (file)
@@ -10,7 +10,7 @@
                     <field name="date" on_change="on_change_date(date)"/>
                     <field name="user_id" on_change="on_change_user_id(user_id)" required="1" options='{"no_open": True}'/>
                     <field name="name"/>
-                    <field domain="[('type','=','normal'),('use_timesheets','=',1)]" name="account_id" context="{'default_use_timesheets': 1, 'default_type': 'contract'}"/>
+                    <field domain="[('type','in',['normal','contract']),('state', '&lt;&gt;', 'close'),('use_timesheets','=',1)]" name="account_id" context="{'default_use_timesheets': 1, 'default_type': 'contract'}"/>
                     <field name="unit_amount" string="Duration" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" sum="Total time" widget="float_time"/>
                     <field name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" invisible="1"/>
                     <field name="journal_id" invisible="1"/>
@@ -47,7 +47,7 @@
                                 </div>
                             </group>
                             <group string="Accounting">
-                                <field domain="[('type','=','normal'),('state', '&lt;&gt;', 'close'),('parent_id','!=',False)]" name="account_id" select="1"/>
+                                <field domain="[('type','in',['normal','contract']),('state', '&lt;&gt;', 'close'),('use_timesheets','=',1)]" name="account_id" select="1" context="{'default_use_timesheets': 1, 'default_type': 'contract'}"/>
                                 <field name="amount"/>
                                 <field name="general_account_id"/>
                                 <field name="journal_id"/>
index f67c129..eaca283 100644 (file)
@@ -168,6 +168,7 @@ class account_analytic_line(osv.osv):
         fiscal_pos_obj = self.pool.get('account.fiscal.position')
         product_uom_obj = self.pool.get('product.uom')
         invoice_line_obj = self.pool.get('account.invoice.line')
+        res_partner_obj = self.pool.get('res.partner')
         invoices = []
         if context is None:
             context = {}
@@ -184,9 +185,20 @@ class account_analytic_line(osv.osv):
         for journal_type, account_ids in journal_types.items():
             for account in analytic_account_obj.browse(cr, uid, list(account_ids), context=context):
                 partner = account.partner_id
+
                 if (not partner) or not (account.pricelist_id):
                     raise osv.except_osv(_('Analytic Account Incomplete!'),
                             _('Contract incomplete. Please fill in the Customer and Pricelist fields.'))
+                context2 = context.copy()
+                context2['lang'] = account.partner_id.lang
+                # set company_id in context, so the correct default journal will be selected
+                # when creating the invoice
+                context2['company_id'] = account.company_id.id
+                # set force_company in context so the correct properties are selected
+                # (eg. income account, receivable account)
+                context2['force_company'] = account.company_id.id
+
+                partner = res_partner_obj.browse(cr, uid, account.partner_id.id, context=context2)
 
                 date_due = False
                 if partner.property_payment_term:
@@ -208,12 +220,6 @@ class account_analytic_line(osv.osv):
                     'date_due': date_due,
                     'fiscal_position': account.partner_id.property_account_position.id
                 }
-                context2 = context.copy()
-                context2['lang'] = partner.lang
-                # set company_id in context, so the correct default journal will be selected
-                context2['force_company'] = curr_invoice['company_id']
-                # set force_company in context so the correct product properties are selected (eg. income account)
-                context2['company_id'] = curr_invoice['company_id']
 
                 last_invoice = invoice_obj.create(cr, uid, curr_invoice, context=context2)
                 invoices.append(last_invoice)
diff --git a/addons/hr_timesheet_invoice/tests/__init__.py b/addons/hr_timesheet_invoice/tests/__init__.py
new file mode 100644 (file)
index 0000000..3b648e7
--- /dev/null
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from . import test_multi_company
+
+checks = [
+    test_multi_company,
+]
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/hr_timesheet_invoice/tests/test_multi_company.py b/addons/hr_timesheet_invoice/tests/test_multi_company.py
new file mode 100644 (file)
index 0000000..8999711
--- /dev/null
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.tests import common
+
+
+class test_multi_company(common.TransactionCase):
+
+    QTY = 5.0
+    PRICE = 75
+
+    def prepare(self):
+        # super(test_multi_company, self).setUp()
+
+        self.company_obj = self.registry('res.company')
+        self.analytic_account_obj = self.registry('account.analytic.account')
+        self.analytic_line_obj = self.registry('account.analytic.line')
+        self.invoice_obj = self.registry('account.invoice')
+        self.product_obj = self.registry('product.product')
+
+        # load main company
+        self.company_a = self.browse_ref('base.main_company')
+        # create an analytic account
+        self.aa_id = self.analytic_account_obj.create(self.cr, self.uid, {
+                'name': 'Project',
+                'company_id': self.company_a.id,
+                'partner_id': self.ref('base.res_partner_2'),
+                'pricelist_id': self.ref('product.list0'),
+            })
+        # set a known price on product
+        self.product_obj.write(self.cr, self.uid, self.ref('product.product_product_consultant'), {
+                'list_price': self.PRICE,
+            })
+
+    def create_invoice(self):
+        # create an analytic line to invoice
+        line_id = self.analytic_line_obj.create(self.cr, self.uid, {
+                'account_id': self.aa_id,
+                'amount': -1.0,
+                'general_account_id': self.ref('account.a_expense'),
+                'journal_id': self.ref('hr_timesheet.analytic_journal'),
+                'name': 'some work',
+                'product_id': self.ref('product.product_product_consultant'),
+                'product_uom_id': self.ref('product.product_uom_hour'),
+                'to_invoice': self.ref('hr_timesheet_invoice.timesheet_invoice_factor2'),  # 50%
+                'unit_amount': self.QTY,
+            })
+        # XXX too strong coupling with UI?
+        wizard_obj = self.registry('hr.timesheet.invoice.create')
+        wizard_id = wizard_obj.create(self.cr, self.uid, {
+                'date': True,
+                'name': True,
+                'price': True,
+                'time': True,
+            }, context={'active_ids': [line_id]})
+        act_win = wizard_obj.do_create(self.cr, self.uid, [wizard_id], context={'active_ids': [line_id]})
+        invoice_ids = self.invoice_obj.search(self.cr, self.uid, act_win['domain'])
+        invoices = self.invoice_obj.browse(self.cr, self.uid, invoice_ids)
+        self.assertEquals(1, len(invoices))
+        return invoices[0]
+
+    def test_00(self):
+        """ invoice task work basic test """
+        self.prepare()
+        invoice = self.create_invoice()
+        self.assertEquals(round(self.QTY * self.PRICE * 0.5, 2), invoice.amount_untaxed)
+
+    def test_01(self):
+        """ invoice task work for analytic account of other company """
+        self.prepare()
+        # create a company B with its own account chart
+        self.company_b_id = self.company_obj.create(self.cr, self.uid, {'name': 'Company B'})
+        self.company_b = self.company_obj.browse(self.cr, self.uid, self.company_b_id)
+        mc_wizard = self.registry('wizard.multi.charts.accounts')
+        mc_wizard_id = mc_wizard.create(self.cr, self.uid, {
+                'company_id': self.company_b_id,
+                'chart_template_id': self.ref('account.conf_chart0'),
+                'code_digits': 2,
+                'sale_tax': self.ref('account.itaxs'),
+                'purchase_tax': self.ref('account.otaxs'),
+                # 'complete_tax_set': config.complete_tax_set,
+                'currency_id': self.company_b.currency_id.id,
+            })
+        mc_wizard.execute(self.cr, self.uid, [mc_wizard_id])
+        # set our analytic account on company B
+        self.analytic_account_obj.write(self.cr, self.uid, [self.aa_id], {
+                'company_id': self.company_b_id,
+            })
+        invoice = self.create_invoice()
+        self.assertEquals(self.company_b_id, invoice.company_id.id, "invoice created for wrong company")
+        self.assertEquals(self.company_b_id, invoice.journal_id.company_id.id, "invoice created with journal of wrong company")
+        self.assertEquals(self.company_b_id, invoice.invoice_line[0].account_id.company_id.id, "invoice line created with account of wrong company")
+        self.assertEquals(self.company_b_id, invoice.account_id.company_id.id, "invoice line created with partner account of wrong company")
+        # self.assertEquals(self.company_b_id, invoice.fiscal_position.company_id.id, "invoice line created with fiscal position of wrong company")
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index ba0a412..db3e7bf 100644 (file)
@@ -340,9 +340,20 @@ class hr_timesheet_line(osv.osv):
     def _check_sheet_state(self, cr, uid, ids, context=None):
         if context is None:
             context = {}
-        for timesheet_line in self.browse(cr, uid, ids, context=context):
-            if timesheet_line.sheet_id and timesheet_line.sheet_id.state not in ('draft', 'new'):
-                return False
+        ts_sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
+        # When a timesheet_line is created from view tree of
+        # hr.analytic.timesheet sheet_id is not defined at this stage
+        # here we check the state of the timesheet for the given date
+        # Furthermore we don't want a default sheet_id allowing to bypass
+        # the check so we recompute all sheet_ids
+        sheet_ids = self._sheet(cr ,uid, ids, False, False, context=context)
+        for ts_line_id, sheet in sheet_ids.iteritems():
+
+            if sheet:
+                ts_sheet = ts_sheet_obj.browse(cr, uid, sheet[0], context=context)
+                if ts_sheet.state not in ('draft', 'new'):
+                    return False
+
         return True
 
     _constraints = [
index 1b4f733..67f0b43 100644 (file)
                             <field context="{'employee_id': employee_id, 'user_id':user_id, 'timesheet_date_from': date_from, 'timesheet_date_to': date_to}" name="timesheet_ids" nolabel="1">
                                 <tree editable="top" string="Timesheet Activities">
                                     <field name="date"/>
-                                    <field domain="[('type','in',['normal', 'contract']), ('state', '&lt;&gt;', 'close'),('use_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id, user_id)" context="{'default_use_timesheets': 1}"/>
+                                    <field domain="[('type','in',['normal', 'contract']), ('state', '&lt;&gt;', 'close'), ('use_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id, user_id)" context="{'default_use_timesheets': 1, 'default_type': 'contract'}"/>
                                     <field name="name"/>
                                     <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time" string="Hours" sum="Hours"/>
                                     <field name="to_invoice" widget="selection"/>
                                 </tree>
                                 <form string="Timesheet Activities" version="7.0">
                                     <field name="date"/>
-                                    <field domain="[('type','=','normal'), ('state', '&lt;&gt;', 'close')]" name="account_id" on_change="on_change_account_id(account_id, user_id)"/>
+                                    <field domain="[('type','in',['normal', 'contract']), ('state', '&lt;&gt;', 'close'), ('use_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id, user_id)" context="{'default_use_timesheets': 1, 'default_type': 'contract'}"/>
                                     <field name="name"/>
                                     <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time"/>
                                     <field name="to_invoice"/>
index 4eaffd3..4b62ee0 100644 (file)
                        <field name="inherit_id" ref="account.view_account_tax_template_form"/>
                        <field name="arch" type="xml">
                                <field position="after" name="price_include">
-                                       <field groups="base.group_extended" name="tax_discount"/>
+                                       <field name="tax_discount"/>
                            </field>
                        <field name="tax_code_id" position="replace" >
                                        <field name="tax_code_id" on_change="onchange_tax_code_id(tax_code_id)" />
                            </field>
-                               <field position="after" name="amount">
-                                       <field groups="base.group_extended" name="base_reduction"/>
-                                       <field groups="base.group_extended" name="amount_mva"/>
+                               <field position="after" name="tax_discount">
+                                       <field name="base_reduction"/>
+                                       <field name="amount_mva"/>
                            </field>
                        </field>
                </record>
                        <field name="inherit_id" ref="account.view_tax_form"/>
                        <field name="arch" type="xml">
                                <field position="after" name="price_include">
-                                       <field groups="base.group_extended" name="tax_discount"/>
+                                       <field name="tax_discount"/>
                            </field>
                                <field name="tax_code_id" position="replace" >
                                        <field name="tax_code_id" on_change="onchange_tax_code_id(tax_code_id)" />
                            </field>
-                           <field position="after" name="amount">
-                                       <field groups="base.group_extended" name="base_reduction"/>
-                                       <field groups="base.group_extended" name="amount_mva"/>
+                           <field position="after" name="tax_discount">
+                                       <field name="base_reduction"/>
+                                       <field name="amount_mva"/>
                            </field>
                        </field>
                </record>
index c3ecf44..cccfcfa 100644 (file)
                        <field ref="tax_code_template_ipi" name="ref_tax_code_id"/>
                </record>
 
+               <record id="tax_template_out_ipi24" model="account.tax.template">
+                       <field name="description">IPI 24%</field>
+                       <field name="name">IPI Saída 24%</field>
+                       <field name="amount">0.24</field>
+                       <field name="type_tax_use">sale</field>
+                       <field ref="account_template_201010301" name="account_collected_id"/>
+                       <field ref="account_template_101050502" name="account_paid_id"/>
+                       <field eval="0" name="price_include"/>
+                       <field eval="0" name="tax_discount"/>
+                       <field ref="l10n_br_account_chart_template" name="chart_template_id"/>
+                       <field ref="tax_code_template_ipi" name="base_code_id"/>
+                       <field ref="tax_code_template_ipi" name="tax_code_id"/>
+                       <field ref="tax_code_template_ipi" name="ref_base_code_id"/>
+                       <field ref="tax_code_template_ipi" name="ref_tax_code_id"/>
+               </record>
+
                <record id="tax_template_out_ipi25" model="account.tax.template">
                        <field name="description">IPI 25%</field>
                        <field name="name">IPI Saída 25%</field>
                        <field ref="tax_code_template_ipi" name="ref_tax_code_id"/>
                </record>
 
+       <record id="tax_template_out_ipi300" model="account.tax.template">
+                       <field name="description">IPI 300%</field>
+                       <field name="name">IPI Saída 300%</field>
+                       <field name="amount">3.00</field>
+                       <field name="type_tax_use">sale</field>
+                       <field ref="account_template_201010301" name="account_collected_id"/>
+                       <field ref="account_template_101050502" name="account_paid_id"/>
+                       <field eval="0" name="price_include"/>
+                       <field eval="0" name="tax_discount"/>
+                       <field ref="l10n_br_account_chart_template" name="chart_template_id"/>
+                       <field ref="tax_code_template_ipi" name="base_code_id"/>
+                       <field ref="tax_code_template_ipi" name="tax_code_id"/>
+                       <field ref="tax_code_template_ipi" name="ref_base_code_id"/>
+                       <field ref="tax_code_template_ipi" name="ref_tax_code_id"/>
+               </record>
+
                <record id="tax_template_out_ipi330" model="account.tax.template">
                        <field name="description">IPI 330%</field>
                        <field name="name">IPI Saída 330%</field>
                        <field ref="tax_code_template_ipi" name="ref_tax_code_id"/>
                </record>
 
+               <record id="tax_template_in_ipi24" model="account.tax.template">
+                       <field name="description">IPI 24%</field>
+                       <field name="name">IPI Entrada 24%</field>
+                       <field name="amount">0.24</field>
+                       <field name="type_tax_use">purchase</field>
+                       <field ref="account_template_101050502" name="account_collected_id"/>
+                       <field ref="account_template_201010301" name="account_paid_id"/>
+                       <field eval="0" name="price_include"/>
+                       <field eval="0" name="tax_discount"/>
+                       <field ref="l10n_br_account_chart_template" name="chart_template_id"/>
+                       <field ref="tax_code_template_ipi" name="base_code_id"/>
+                       <field ref="tax_code_template_ipi" name="tax_code_id"/>
+                       <field ref="tax_code_template_ipi" name="ref_base_code_id"/>
+                       <field ref="tax_code_template_ipi" name="ref_tax_code_id"/>
+               </record>
+
                <record id="tax_template_in_ipi25" model="account.tax.template">
                        <field name="description">IPI 25%</field>
                        <field name="name">IPI Entrada 25%</field>
                        <field ref="tax_code_template_ipi" name="ref_tax_code_id"/>
                </record>
 
+               <record id="tax_template_in_ipi300" model="account.tax.template">
+                       <field name="description">IPI 300%</field>
+                       <field name="name">IPI Entrada 300%</field>
+                       <field name="amount">3.00</field>
+                       <field name="type_tax_use">purchase</field>
+                       <field ref="account_template_101050502" name="account_collected_id"/>
+                       <field ref="account_template_201010301" name="account_paid_id"/>
+                       <field eval="0" name="price_include"/>
+                       <field eval="0" name="tax_discount"/>
+                       <field ref="l10n_br_account_chart_template" name="chart_template_id"/>
+                       <field ref="tax_code_template_ipi" name="base_code_id"/>
+                       <field ref="tax_code_template_ipi" name="tax_code_id"/>
+                       <field ref="tax_code_template_ipi" name="ref_base_code_id"/>
+                       <field ref="tax_code_template_ipi" name="ref_tax_code_id"/>
+               </record>
+
                <record id="tax_template_in_ipi330" model="account.tax.template">
                        <field name="description">IPI 330%</field>
                        <field name="name">IPI Entrada 330%</field>
index b06a3fe..b2127d4 100644 (file)
@@ -1,14 +1,14 @@
 id,code,name,parent_id:id,user_type:id,type,reconcile
 0,0,Azienda,,account.data_account_type_view,view,FALSE
-1,1,ATTIVO ,0,account.data_account_type_view,view,TRUE
-11,11,IMMOBILIZZAZIONI IMMATERIALI ,1,account.data_account_type_view,view,TRUE
+1,1,ATTIVO ,0,account.account_type_asset_view1,view,TRUE
+11,11,IMMOBILIZZAZIONI IMMATERIALI ,1,account.account_type_asset_view1,view,TRUE
 1101,1101,costi di impianto ,11,account.data_account_type_asset,other,TRUE
 1106,1106,software ,11,account.data_account_type_asset,other,TRUE
 1108,1108,avviamento ,11,account.data_account_type_asset,other,TRUE
 1111,1111,fondo ammortamento costi di impianto ,11,account.data_account_type_asset,other,TRUE
 1116,1116,fondo ammortamento software ,11,account.data_account_type_asset,other,TRUE
 1118,1118,fondo ammortamento avviamento ,11,account.data_account_type_asset,other,TRUE
-12,12,IMMOBILIZZAZIONI MATERIALI ,1,account.data_account_type_view,view,TRUE
+12,12,IMMOBILIZZAZIONI MATERIALI ,1,account.account_type_asset_view1,view,TRUE
 1201,1201,fabbricati ,12,account.data_account_type_asset,other,TRUE
 1202,1202,impianti e macchinari ,12,account.data_account_type_asset,other,TRUE
 1204,1204,attrezzature commerciali ,12,account.data_account_type_asset,other,TRUE
@@ -24,13 +24,13 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 1217,1217,fondo ammortamento automezzi ,12,account.data_account_type_asset,other,TRUE
 1218,1218,fondo ammortamento imballaggi durevoli ,12,account.data_account_type_asset,other,TRUE
 1220,1220,fornitori immobilizzazioni c/acconti ,12,account.data_account_type_asset,other,TRUE
-13,13,IMMOBILIZZAZIONI FINANZIARIE ,1,account.data_account_type_view,view,TRUE
+13,13,IMMOBILIZZAZIONI FINANZIARIE ,1,account.account_type_asset_view1,view,TRUE
 1301,1301,mutui attivi ,13,account.data_account_type_asset,other,TRUE
-14,14,RIMANENZE ,1,account.data_account_type_view,view,TRUE
+14,14,RIMANENZE ,1,account.account_type_asset_view1,view,TRUE
 1401,1401,materie di consumo ,14,account.data_account_type_asset,other,TRUE
 1404,1404,merci ,14,account.data_account_type_asset,other,TRUE
 1410,1410,fornitori c/acconti ,14,account.data_account_type_asset,other,TRUE
-15,15,CREDITI COMMERCIALI ,1,account.data_account_type_view,view,TRUE
+15,15,CREDITI COMMERCIALI ,1,account.account_type_asset_view1,view,TRUE
 1501,1501,crediti v/clienti ,15,account.data_account_type_receivable,receivable,TRUE
 1502,1502,crediti commerciali diversi ,15,account.data_account_type_asset,other,TRUE
 1503,1503,clienti c/spese anticipate ,15,account.data_account_type_receivable,receivable,TRUE
@@ -43,7 +43,7 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 1531,1531,crediti da liquidare ,15,account.data_account_type_asset,other,TRUE
 1540,1540,fondo svalutazione crediti ,15,account.data_account_type_asset,other,TRUE
 1541,1541,fondo rischi su crediti ,15,account.data_account_type_asset,other,TRUE
-16,16,CREDITI DIVERSI ,1,account.data_account_type_view,view,TRUE
+16,16,CREDITI DIVERSI ,1,account.account_type_asset_view1,view,TRUE
 1601,1601,IVA n/credito ,16,account.data_account_type_asset,other,TRUE
 1602,1602,IVA c/acconto ,16,account.data_account_type_asset,other,TRUE
 1605,1605,crediti per IVA ,16,account.data_account_type_asset,other,TRUE
@@ -54,30 +54,30 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 1620,1620,personale c/acconti ,16,account.data_account_type_asset,other,TRUE
 1630,1630,crediti v/istituti previdenziali ,16,account.data_account_type_asset,other,TRUE
 1640,1640,debitori diversi ,16,account.data_account_type_receivable,receivable,TRUE
-18,18,DISPONIBILITÀ LIQUIDE ,1,account.data_account_type_view,view,TRUE
+18,18,DISPONIBILITÀ LIQUIDE ,1,account.account_type_asset_view1,view,TRUE
 1801,1801,banche c/c ,18,account.data_account_type_bank,liquidity,TRUE
 1810,1810,c/c postali ,18,account.data_account_type_bank,liquidity,TRUE
 1820,1820,denaro in cassa ,18,account.data_account_type_cash,liquidity,TRUE
 1821,1821,assegni ,18,account.data_account_type_cash,liquidity,TRUE
 1822,1822,valori bollati ,18,account.data_account_type_cash,liquidity,TRUE
-19,19,RATEI E RISCONTI ATTIVI ,1,account.data_account_type_view,view,TRUE
+19,19,RATEI E RISCONTI ATTIVI ,1,account.account_type_asset_view1,view,TRUE
 1901,1901,ratei attivi ,19,account.data_account_type_asset,other,TRUE
 1902,1902,risconti attivi ,19,account.data_account_type_asset,other,TRUE
-2,2,PASSIVO ,0,account.data_account_type_view,view,TRUE
-20,20,PATRIMONIO NETTO ,2,account.data_account_type_view,view,TRUE
+2,2,PASSIVO ,0,account.account_type_liability_view1,view,TRUE
+20,20,PATRIMONIO NETTO ,2,account.account_type_liability_view1,view,TRUE
 2101,2101,patrimonio netto ,20,account.data_account_type_liability,other,TRUE
 2102,2102,utile d'esercizio ,20,account.data_account_type_liability,other,TRUE
 2103,2103,perdita d'esercizio ,20,account.data_account_type_liability,other,TRUE
 2104,2104,prelevamenti extra gestione ,20,account.data_account_type_liability,other,TRUE
 2105,2105,titolare c/ritenute subite ,20,account.data_account_type_liability,other,TRUE
-22,22,FONDI PER RISCHI E ONERI ,2,account.data_account_type_view,view,TRUE
+22,22,FONDI PER RISCHI E ONERI ,2,account.account_type_liability_view1,view,TRUE
 2201,2201,fondo per imposte ,22,account.data_account_type_liability,other,TRUE
 2204,2204,fondo responsabilità civile ,22,account.data_account_type_liability,other,TRUE
 2205,2205,fondo spese future ,22,account.data_account_type_liability,other,TRUE
 2211,2211,fondo manutenzioni programmate ,22,account.data_account_type_liability,other,TRUE
-23,23,TRATTAMENTO FINE RAPPORTO DI LAVORO ,2,account.data_account_type_view,view,TRUE
+23,23,TRATTAMENTO FINE RAPPORTO DI LAVORO ,2,account.account_type_liability_view1,view,TRUE
 2301,2301,debiti per TFRL ,23,account.data_account_type_liability,other,TRUE
-24,24,DEBITI FINANZIARI ,2,account.data_account_type_view,view,TRUE
+24,24,DEBITI FINANZIARI ,2,account.account_type_liability_view1,view,TRUE
 2410,2410,mutui passivi ,24,account.data_account_type_liability,other,TRUE
 2411,2411,banche c/sovvenzioni ,24,account.data_account_type_liability,other,TRUE
 2420,2420,banche c/c passivi ,24,account.data_account_type_liability,other,TRUE
@@ -85,13 +85,13 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 2422,2422,banche c/cambiali all'incasso ,24,account.data_account_type_liability,other,TRUE
 2423,2423,banche c/anticipi su fatture ,24,account.data_account_type_liability,other,TRUE
 2440,2440,debiti v/altri finanziatori ,24,account.data_account_type_liability,other,TRUE
-25,25,DEBITI COMMERCIALI ,2,account.data_account_type_view,view,TRUE
+25,25,DEBITI COMMERCIALI ,2,account.account_type_liability_view1,view,TRUE
 2501,2501,debiti v/fornitori ,25,account.data_account_type_payable,payable,TRUE
 2503,2503,cambiali passive ,25,account.data_account_type_liability,other,TRUE
 2520,2520,fatture da ricevere ,25,account.data_account_type_liability,other,TRUE
 2521,2521,debiti da liquidare ,25,account.data_account_type_liability,other,TRUE
 2530,2530,clienti c/acconti ,25,account.data_account_type_payable,payable,TRUE
-26,26,DEBITI DIVERSI ,2,account.data_account_type_view,view,TRUE
+26,26,DEBITI DIVERSI ,2,account.account_type_liability_view1,view,TRUE
 2601,2601,IVA n/debito ,26,account.data_account_type_liability,other,TRUE
 2602,2602,debiti per ritenute da versare ,26,account.data_account_type_payable,payable,TRUE
 2605,2605,erario c/IVA ,26,account.data_account_type_payable,payable,TRUE
@@ -102,10 +102,10 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 2622,2622,clienti c/cessione ,26,account.data_account_type_liability,other,TRUE
 2630,2630,debiti v/istituti previdenziali ,26,account.data_account_type_liability,other,TRUE
 2640,2640,creditori diversi ,26,account.data_account_type_payable,payable,TRUE
-27,27,RATEI E RISCONTI PASSIVI ,2,account.data_account_type_view,view,TRUE
+27,27,RATEI E RISCONTI PASSIVI ,2,account.account_type_liability_view1,view,TRUE
 2701,2701,ratei passivi ,27,account.data_account_type_liability,other,TRUE
 2702,2702,risconti passivi ,27,account.data_account_type_liability,other,TRUE
-28,28,CONTI TRANSITORI E DIVERSI ,2,account.data_account_type_view,view,TRUE
+28,28,CONTI TRANSITORI E DIVERSI ,2,account.account_type_liability_view1,view,TRUE
 2801,2801,bilancio di apertura ,28,account.data_account_type_liability,other,TRUE
 2802,2802,bilancio di chiusura ,28,account.data_account_type_liability,other,TRUE
 2810,2810,IVA c/liquidazioni ,28,account.data_account_type_liability,other,TRUE
@@ -113,7 +113,7 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 2820,2820,banca ... c/c ,28,account.data_account_type_liability,other,TRUE
 2821,2821,banca ... c/c ,28,account.data_account_type_liability,other,TRUE
 2822,2822,banca ... c/c ,28,account.data_account_type_liability,other,TRUE
-29,29,CONTI DEI SISTEMI SUPPLEMENTARI ,2,account.data_account_type_view,view,TRUE
+29,29,CONTI DEI SISTEMI SUPPLEMENTARI ,2,account.account_type_liability_view1,view,TRUE
 2901,2901,beni di terzi ,29,account.data_account_type_liability,other,TRUE
 2902,2902,depositanti beni ,29,account.data_account_type_liability,other,TRUE
 2911,2911,merci da ricevere ,29,account.data_account_type_liability,other,TRUE
@@ -128,22 +128,22 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 2927,2927,creditori per fideiussioni ,29,account.data_account_type_liability,other,TRUE
 2931,2931,rischi per avalli ,29,account.data_account_type_liability,other,TRUE
 2932,2932,creditori per avalli ,29,account.data_account_type_liability,other,TRUE
-3,3,VALORE DELLA PRODUZIONE ,0,account.data_account_type_view,view,TRUE
-31,31,VENDITE E PRESTAZIONI ,3,account.data_account_type_view,view,TRUE
+3,3,VALORE DELLA PRODUZIONE ,0,account.account_type_income_view1,view,TRUE
+31,31,VENDITE E PRESTAZIONI ,3,account.account_type_income_view1,view,TRUE
 3101,3101,merci c/vendite ,31,account.data_account_type_income,other,TRUE
 3103,3103,rimborsi spese di vendita ,31,account.data_account_type_income,other,TRUE
 3110,3110,resi su vendite ,31,account.data_account_type_income,other,TRUE
 3111,3111,ribassi e abbuoni passivi ,31,account.data_account_type_income,other,TRUE
 3112,3112,premi su vendite ,31,account.data_account_type_income,other,TRUE
-32,32,RICAVI E PROVENTI DIVERSI ,3,account.data_account_type_view,view,TRUE
+32,32,RICAVI E PROVENTI DIVERSI ,3,account.account_type_income_view1,view,TRUE
 3201,3201,fitti attivi ,32,account.data_account_type_income,other,TRUE
 3202,3202,proventi vari ,32,account.data_account_type_income,other,TRUE
 3210,3210,arrotondamenti attivi ,32,account.data_account_type_income,other,TRUE
 3220,3220,plusvalenze ordinarie diverse ,32,account.data_account_type_income,other,TRUE
 3230,3230,sopravvenienze attive ordinarie diverse ,32,account.data_account_type_income,other,TRUE
 3240,3240,insussistenze attive ordinarie diverse ,32,account.data_account_type_income,other,TRUE
-4,4,COSTI DELLA PRODUZIONE ,0,account.data_account_type_view,view,TRUE
-41,41,COSTO DEL VENDUTO ,4,account.data_account_type_view,view,TRUE
+4,4,COSTI DELLA PRODUZIONE ,0,account.account_type_expense_view1,view,TRUE
+41,41,COSTO DEL VENDUTO ,4,account.account_type_expense_view1,view,TRUE
 4101,4101,merci c/acquisti ,41,account.data_account_type_expense,other,TRUE
 4102,4102,materie di consumo c/acquisti ,41,account.data_account_type_expense,other,TRUE
 4105,4105,merci c/apporti ,41,account.data_account_type_expense,other,TRUE
@@ -154,7 +154,7 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 4122,4122,materie di consumo c/esistenze iniziali ,41,account.data_account_type_expense,other,TRUE
 4131,4131,merci c/rimanenze finali ,41,account.data_account_type_expense,other,TRUE
 4132,4132,materie di consumo c/rimanenze finali ,41,account.data_account_type_expense,other,TRUE
-42,42,COSTI PER SERVIZI ,4,account.data_account_type_view,view,TRUE
+42,42,COSTI PER SERVIZI ,4,account.account_type_expense_view1,view,TRUE
 4201,4201,costi di trasporto ,42,account.data_account_type_expense,other,TRUE
 4202,4202,costi per energia ,42,account.data_account_type_expense,other,TRUE
 4203,4203,costi di pubblicità ,42,account.data_account_type_expense,other,TRUE
@@ -168,19 +168,19 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 4211,4211,costi di manutenzione e riparazione ,42,account.data_account_type_expense,other,TRUE
 4212,4212,provvigioni passive ,42,account.data_account_type_expense,other,TRUE
 4213,4213,spese di incasso ,42,account.data_account_type_expense,other,TRUE
-43,43,COSTI PER GODIMENTO BENI DI TERZI ,4,account.data_account_type_view,view,TRUE
+43,43,COSTI PER GODIMENTO BENI DI TERZI ,4,account.account_type_expense_view1,view,TRUE
 4301,4301,fitti passivi ,43,account.data_account_type_expense,other,TRUE
 4302,4302,canoni di leasing ,43,account.data_account_type_expense,other,TRUE
-44,44,COSTI PER IL PERSONALE ,4,account.data_account_type_view,view,TRUE
+44,44,COSTI PER IL PERSONALE ,4,account.account_type_expense_view1,view,TRUE
 4401,4401,salari e stipendi ,44,account.data_account_type_expense,other,TRUE
 4402,4402,oneri sociali ,44,account.data_account_type_expense,other,TRUE
 4403,4403,TFRL ,44,account.data_account_type_expense,other,TRUE
 4404,4404,altri costi per il personale ,44,account.data_account_type_expense,other,TRUE
-45,45,AMMORTAMENTI IMMOBILIZZAZIONI IMMATERIALI ,4,account.data_account_type_view,view,TRUE
+45,45,AMMORTAMENTI IMMOBILIZZAZIONI IMMATERIALI ,4,account.account_type_expense_view1,view,TRUE
 4501,4501,ammortamento costi di impianto ,45,account.data_account_type_expense,other,TRUE
 4506,4506,ammortamento software ,45,account.data_account_type_expense,other,TRUE
 4508,4508,ammortamento avviamento ,45,account.data_account_type_expense,other,TRUE
-46,46,AMMORTAMENTI IMMOBILIZZAZIONI MATERIALI ,4,account.data_account_type_view,view,TRUE
+46,46,AMMORTAMENTI IMMOBILIZZAZIONI MATERIALI ,4,account.account_type_expense_view1,view,TRUE
 4601,4601,ammortamento fabbricati ,46,account.data_account_type_expense,other,TRUE
 4602,4602,ammortamento impianti e macchinari ,46,account.data_account_type_expense,other,TRUE
 4604,4604,ammortamento attrezzature commerciali ,46,account.data_account_type_expense,other,TRUE
@@ -188,17 +188,17 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 4606,4606,ammortamento arredamento ,46,account.data_account_type_expense,other,TRUE
 4607,4607,ammortamento automezzi ,46,account.data_account_type_expense,other,TRUE
 4608,4608,ammortamento imballaggi durevoli ,46,account.data_account_type_expense,other,TRUE
-47,47,SVALUTAZIONI ,4,account.data_account_type_view,view,TRUE
+47,47,SVALUTAZIONI ,4,account.account_type_expense_view1,view,TRUE
 4701,4701,svalutazioni immobilizzazioni immateriali ,47,account.data_account_type_expense,other,TRUE
 4702,4702,svalutazioni immobilizzazioni materiali ,47,account.data_account_type_expense,other,TRUE
 4706,4706,svalutazione crediti ,47,account.data_account_type_expense,other,TRUE
-48,48,ACCANTONAMENTI ,4,account.data_account_type_view,view,TRUE
-481,481,ACCANTONAMENTI PER RISCHI ,48,account.data_account_type_view,view,TRUE
+48,48,ACCANTONAMENTI ,4,account.account_type_expense_view1,view,TRUE
+481,481,ACCANTONAMENTI PER RISCHI ,48,account.account_type_expense_view1,view,TRUE
 4814,4814,accantonamento per responsabilità civile ,481,account.data_account_type_expense,other,TRUE
-482,482,ALTRI ACCANTONAMENTI ,48,account.data_account_type_view,view,TRUE
+482,482,ALTRI ACCANTONAMENTI ,48,account.account_type_expense_view1,view,TRUE
 4821,4821,accantonamento per spese future ,482,account.data_account_type_expense,other,TRUE
 4823,4823,accantonamento per manutenzioni programmate ,482,account.data_account_type_expense,other,TRUE
-49,49,ONERI DIVERSI ,4,account.data_account_type_view,view,TRUE
+49,49,ONERI DIVERSI ,4,account.account_type_expense_view1,view,TRUE
 4901,4901,oneri fiscali diversi ,49,account.data_account_type_expense,other,TRUE
 4903,4903,oneri vari ,49,account.data_account_type_expense,other,TRUE
 4905,4905,perdite su crediti ,49,account.data_account_type_expense,other,TRUE
@@ -206,30 +206,30 @@ id,code,name,parent_id:id,user_type:id,type,reconcile
 4920,4920,minusvalenze ordinarie diverse ,49,account.data_account_type_expense,other,TRUE
 4930,4930,sopravvenienze passive ordinarie diverse ,49,account.data_account_type_expense,other,TRUE
 4940,4940,insussistenze passive ordinarie diverse ,49,account.data_account_type_expense,other,TRUE
-5,5,PROVENTI E ONERI FINANZIARI ,0,account.data_account_type_view,view,TRUE
-51,51,PROVENTI FINANZIARI ,5,account.data_account_type_view,view,TRUE
+5,5,PROVENTI E ONERI FINANZIARI ,0,account.account_type_income_view1,view,TRUE
+51,51,PROVENTI FINANZIARI ,5,account.account_type_income_view1,view,TRUE
 5110,5110,interessi attivi v/clienti ,51,account.data_account_type_income,other,TRUE
 5115,5115,interessi attivi bancari ,51,account.data_account_type_income,other,TRUE
 5116,5116,interessi attivi postali ,51,account.data_account_type_income,other,TRUE
 5140,5140,proventi finanziari diversi ,51,account.data_account_type_income,other,TRUE
-52,52,ONERI FINANZIARI ,5,account.data_account_type_view,view,TRUE
+52,52,ONERI FINANZIARI ,5,account.account_type_expense_view1,view,TRUE
 5201,5201,interessi passivi v/fornitori ,52,account.data_account_type_expense,other,TRUE
 5202,5202,interessi passivi bancari ,52,account.data_account_type_expense,other,TRUE
 5203,5203,sconti passivi bancari ,52,account.data_account_type_expense,other,TRUE
 5210,5210,interessi passivi su mutui ,52,account.data_account_type_expense,other,TRUE
 5240,5240,oneri finanziari diversi ,52,account.data_account_type_expense,other,TRUE
-7,7,PROVENTI E ONERI STRAORDINARI ,0,account.data_account_type_view,view,TRUE
-71,71,PROVENTI STRAORDINARI ,7,account.data_account_type_view,view,TRUE
+7,7,PROVENTI E ONERI STRAORDINARI ,0,account.account_type_income_view1,view,TRUE
+71,71,PROVENTI STRAORDINARI ,7,account.account_type_income_view1,view,TRUE
 7101,7101,plusvalenze straordinarie ,71,account.data_account_type_income,other,TRUE
 7102,7102,sopravvenienze attive straordinarie ,71,account.data_account_type_income,other,TRUE
 7103,7103,insussistenze attive straordinarie ,71,account.data_account_type_income,other,TRUE
-72,72,ONERI STRAORDINARI ,7,account.data_account_type_view,view,TRUE
+72,72,ONERI STRAORDINARI ,7,account.account_type_expense_view1,view,TRUE
 7201,7201,minusvalenze straordinarie ,72,account.data_account_type_expense,other,TRUE
 7202,7202,sopravvenienze passive straordinarie ,72,account.data_account_type_expense,other,TRUE
 7203,7203,insussistenze passive straordinarie ,72,account.data_account_type_expense,other,TRUE
 7204,7204,imposte esercizi precedenti ,72,account.data_account_type_expense,other,TRUE
-8,8,IMPOSTE DELL'ESERCIZIO ,0,account.data_account_type_view,view,TRUE
+8,8,IMPOSTE DELL'ESERCIZIO ,0,account.account_type_expense_view1,view,TRUE
 8101,8101,imposte dell'esercizio ,8,account.data_account_type_expense,other,TRUE
-9,9,CONTI DI RISULTATO ,0,account.data_account_type_view,view,TRUE
+9,9,CONTI DI RISULTATO ,0,account.account_type_expense_view1,view,TRUE
 9101,9101,conto di risultato economico ,9,account.data_account_type_expense,other,TRUE
 9102,9102,stato patrimoniale,9,account.data_account_type_expense,other,TRUE
index 67daee0..443d93c 100644 (file)
@@ -7,9 +7,7 @@ id,description,chart_template_id:id,name,sequence,amount,parent_id:id,child_depe
 20a,20a,l10n_it_chart_template_generic,Iva al 20% (credito),4,0.2,,False,percent,1601,1601,purchase,template_impcode_pagata_20,template_ivacode_pagata_20,template_impcode_pagata_20,template_ivacode_pagata_20,1,1,False,-1,-1
 10v,10v,l10n_it_chart_template_generic,Iva al 10% (debito),5,0.1,,False,percent,2601,2601,sale,template_impcode_riscossa_10,template_ivacode_riscossa_10,template_impcode_riscossa_10,template_ivacode_riscossa_10,-1,-1,False,1,1
 10a,10a,l10n_it_chart_template_generic,Iva al 10% (credito),6,0.1,,False,percent,1601,1601,purchase,template_impcode_pagata_10,template_ivacode_pagata_10,template_impcode_pagata_10,template_ivacode_pagata_10,1,1,False,-1,-1
-10AO,10AO,l10n_it_chart_template_generic,Iva al 10% indetraibile,7,0.1,,True,percent,,,purchase,template_impcode_pagata_10ind,,template_impcode_pagata_10ind,,1,1,False,-1,-1
-10AOb,10AOb,l10n_it_chart_template_generic,Iva al 10% indetraibile (D),200,0,10AO,False,balance,1601,1601,purchase,,template_ivacode_pagata_10,,template_ivacode_pagata_10,1,1,False,-1,-1
-10AOa,10AOa,l10n_it_chart_template_generic,Iva al 10% indetraibile (I),100,1,10AO,False,percent,,,purchase,,template_ivacode_pagata_10ind,,template_ivacode_pagata_10ind,1,1,False,-1,-1
+10AO,10AO,l10n_it_chart_template_generic,Iva al 10% indetraibile,7,0.1,,False,percent,,,purchase,template_impcode_pagata_10ind,template_ivacode_pagata_10ind,template_impcode_pagata_10ind,template_ivacode_pagata_10ind,1,1,False,-1,-1
 12v,12v,l10n_it_chart_template_generic,Iva 12% (debito),8,0.12,,False,percent,2601,2601,sale,template_impcode_riscossa_12,template_ivacode_riscossa_12,template_impcode_riscossa_12,template_ivacode_riscossa_12,-1,-1,False,1,1
 12a,12a,l10n_it_chart_template_generic,Iva 12% (credito),9,0.12,,False,percent,1601,1601,purchase,template_impcode_pagata_12,template_ivacode_pagata_12,template_impcode_pagata_12,template_ivacode_pagata_12,1,1,False,-1,-1
 2010,2010,l10n_it_chart_template_generic,Iva al 20% detraibile 10%,10,0.2,,True,percent,,,purchase,template_impcode_pagata_20det10,,template_impcode_pagata_20det10,,1,1,False,-1,-1
@@ -21,9 +19,7 @@ id,description,chart_template_id:id,name,sequence,amount,parent_id:id,child_depe
 2040,2040,l10n_it_chart_template_generic,Iva al 20% detraibile 40%,12,0.2,,True,percent,,,purchase,template_impcode_pagata_20det40,,template_impcode_pagata_20det40,,1,1,False,-1,-1
 2040b,2040b,l10n_it_chart_template_generic,Iva al 20% detraibile 40% (D),200,0,2040,False,balance,1601,1601,purchase,,template_ivacode_pagata_20det40,,template_ivacode_pagata_20det40,1,1,False,-1,-1
 2040a,2040a,l10n_it_chart_template_generic,Iva al 20% detraibile 40% (I),100,0.6,2040,False,percent,,,purchase,,template_ivacode_pagata_20det40ind,,template_ivacode_pagata_20det40ind,1,1,False,-1,-1
-20AO,20AO,l10n_it_chart_template_generic,Iva al 20% indetraibile,13,0.2,,True,percent,,,purchase,template_impcode_pagata_20ind,,template_impcode_pagata_20ind,,1,1,False,-1,-1
-20AOb,20AOb,l10n_it_chart_template_generic,Iva al 20% indetraibile (D),200,0,20AO,False,balance,1601,1601,purchase,,template_ivacode_pagata_20ind,,template_ivacode_pagata_20ind,1,1,False,-1,-1
-20AOa,20AOa,l10n_it_chart_template_generic,Iva al 20% indetraibile (I),100,1,20AO,False,percent,,,purchase,,template_ivacode_pagata_20ind,,template_ivacode_pagata_20ind,1,1,False,-1,-1
+20AO,20AO,l10n_it_chart_template_generic,Iva al 20% indetraibile,13,0.2,,False,percent,,,purchase,template_impcode_pagata_20ind,template_ivacode_pagata_20ind,template_impcode_pagata_20ind,template_ivacode_pagata_20ind,1,1,False,-1,-1
 20I5,20I5,l10n_it_chart_template_generic,IVA al 20% detraibile al 50%,14,0.2,,True,percent,,,purchase,template_impcode_pagata_20det50,,template_impcode_pagata_20det50,,1,1,False,-1,-1
 20I5b,20I5b,l10n_it_chart_template_generic,IVA al 20% detraibile al 50% (D),200,0,20I5,False,balance,1601,1601,purchase,,template_ivacode_pagata_20det50,,template_ivacode_pagata_20det50,1,1,False,-1,-1
 20I5a,20I5a,l10n_it_chart_template_generic,IVA al 20% detraibile al 50% (I),100,0.5,20I5,False,percent,,,purchase,,template_ivacode_pagata_20det50ind,,template_ivacode_pagata_20det50ind,1,1,False,-1,-1
@@ -31,9 +27,7 @@ id,description,chart_template_id:id,name,sequence,amount,parent_id:id,child_depe
 2a,2a,l10n_it_chart_template_generic,Iva 2% (credito),16,0.02,,False,percent,1601,1601,purchase,template_impcode_pagata_2,template_ivacode_pagata_2,template_impcode_pagata_2,template_ivacode_pagata_2,1,1,False,-1,-1
 4v,4v,l10n_it_chart_template_generic,Iva 4% (debito),17,0.04,,False,percent,2601,2601,sale,template_impcode_riscossa_4,template_ivacode_riscossa_4,template_impcode_riscossa_4,template_ivacode_riscossa_4,-1,-1,False,1,1
 4a,4a,l10n_it_chart_template_generic,Iva 4% (credito),18,0.04,,False,percent,1601,1601,purchase,template_impcode_pagata_4,template_ivacode_pagata_4,template_impcode_pagata_4,template_ivacode_pagata_4,1,1,False,-1,-1
-4AO,4AO,l10n_it_chart_template_generic,Iva al 4% indetraibile,19,0.04,,True,percent,,,purchase,template_impcode_pagata_4ind,,template_impcode_pagata_4ind,,1,1,False,-1,-1
-4AOb,4AOb,l10n_it_chart_template_generic,Iva al 4% indetraibile (D),200,0,4AO,False,balance,1601,1601,purchase,,template_ivacode_pagata_4ind,,template_ivacode_pagata_4ind,1,1,False,-1,-1
-4AOa,4AOa,l10n_it_chart_template_generic,Iva al 4% indetraibile (I),100,1,4AO,False,percent,,,purchase,,template_ivacode_pagata_4ind,,template_ivacode_pagata_4ind,1,1,False,-1,-1
+4AO,4AO,l10n_it_chart_template_generic,Iva al 4% indetraibile,19,0.04,,False,percent,,,purchase,template_impcode_pagata_4ind,template_ivacode_pagata_4ind,template_impcode_pagata_4ind,template_ivacode_pagata_4ind,1,1,False,-1,-1
 10I5,10I5,l10n_it_chart_template_generic,IVA al 10% detraibile al 50%,20,0.1,,True,percent,,,purchase,template_impcode_pagata_10det50,,template_impcode_pagata_10det50,,1,1,False,-1,-1
 10I5b,10I5b,l10n_it_chart_template_generic,IVA al 10% detraibile al 50% (D),200,0,10I5,False,balance,1601,1601,purchase,,template_ivacode_pagata_10det50,,template_ivacode_pagata_10det50,1,1,False,-1,-1
 10I5a,10I5a,l10n_it_chart_template_generic,IVA al 10% detraibile al 50% (I),100,0.5,10I5,False,percent,,,purchase,,template_ivacode_pagata_10det50ind,,template_ivacode_pagata_10det50ind,1,1,False,-1,-1
@@ -61,9 +55,7 @@ id,description,chart_template_id:id,name,sequence,amount,parent_id:id,child_depe
 2140,2140,l10n_it_chart_template_generic,Iva al 21% detraibile 40%,33,0.21,,True,percent,,,purchase,template_impcode_pagata_21det40,,template_impcode_pagata_21det40,,1,1,False,-1,-1
 2140b,2140b,l10n_it_chart_template_generic,Iva al 21% detraibile 40% (D),200,0,2140,False,balance,1601,1601,purchase,,template_ivacode_pagata_21det40,,template_ivacode_pagata_21det40,1,1,False,-1,-1
 2140a,2140a,l10n_it_chart_template_generic,Iva al 21% detraibile 40% (I),100,0.6,2140,False,percent,,,purchase,,template_ivacode_pagata_21det40ind,,template_ivacode_pagata_21det40ind,1,1,False,-1,-1
-21AO,21AO,l10n_it_chart_template_generic,Iva al 21% indetraibile,34,0.21,,True,percent,,,purchase,template_impcode_pagata_21ind,,template_impcode_pagata_21ind,,1,1,False,-1,-1
-21AOb,21AOb,l10n_it_chart_template_generic,Iva al 21% indetraibile (D),200,0,21AO,False,balance,1601,1601,purchase,,template_ivacode_pagata_21ind,,template_ivacode_pagata_21ind,1,1,False,-1,-1
-21AOa,21AOa,l10n_it_chart_template_generic,Iva al 21% indetraibile (I),100,1,21AO,False,percent,,,purchase,,template_ivacode_pagata_21ind,,template_ivacode_pagata_21ind,1,1,False,-1,-1
+21AO,21AO,l10n_it_chart_template_generic,Iva al 21% indetraibile,34,0.21,,False,percent,,,purchase,template_impcode_pagata_21ind,template_ivacode_pagata_21ind,template_impcode_pagata_21ind,template_ivacode_pagata_21ind,1,1,False,-1,-1
 21I5,21I5,l10n_it_chart_template_generic,IVA al 21% detraibile al 50%,35,0.21,,True,percent,,,purchase,template_impcode_pagata_21det50,,template_impcode_pagata_21det50,,1,1,False,-1,-1
 21I5b,21I5b,l10n_it_chart_template_generic,IVA al 21% detraibile al 50% (D),200,0,21I5,False,balance,1601,1601,purchase,,template_ivacode_pagata_21det50,,template_ivacode_pagata_21det50,1,1,False,-1,-1
 21I5a,21I5a,l10n_it_chart_template_generic,IVA al 21% detraibile al 50% (I),100,0.5,21I5,False,percent,,,purchase,,template_ivacode_pagata_21det50ind,,template_ivacode_pagata_21det50ind,1,1,False,-1,-1
@@ -76,9 +68,7 @@ id,description,chart_template_id:id,name,sequence,amount,parent_id:id,child_depe
 2240,2240,l10n_it_chart_template_generic,Iva al 22% detraibile 40%,33,0.22,,True,percent,,,purchase,template_impcode_pagata_22det40,,template_impcode_pagata_22det40,,1,1,False,-1,-1
 2240b,2240b,l10n_it_chart_template_generic,Iva al 22% detraibile 40% (D),200,0,2240,False,balance,1601,1601,purchase,,template_ivacode_pagata_22det40,,template_ivacode_pagata_22det40,1,1,False,-1,-1
 2240a,2240a,l10n_it_chart_template_generic,Iva al 22% detraibile 40% (I),100,0.6,2240,False,percent,,,purchase,,template_ivacode_pagata_22det40ind,,template_ivacode_pagata_22det40ind,1,1,False,-1,-1
-22AO,22AO,l10n_it_chart_template_generic,Iva al 22% indetraibile,34,0.22,,True,percent,,,purchase,template_impcode_pagata_22ind,,template_impcode_pagata_22ind,,1,1,False,-1,-1
-22AOb,22AOb,l10n_it_chart_template_generic,Iva al 22% indetraibile (D),200,0,22AO,False,balance,1601,1601,purchase,,template_ivacode_pagata_22ind,,template_ivacode_pagata_22ind,1,1,False,-1,-1
-22AOa,22AOa,l10n_it_chart_template_generic,Iva al 22% indetraibile (I),100,1,22AO,False,percent,,,purchase,,template_ivacode_pagata_22ind,,template_ivacode_pagata_22ind,1,1,False,-1,-1
+22AO,22AO,l10n_it_chart_template_generic,Iva al 22% indetraibile,34,0.22,,False,percent,,,purchase,template_impcode_pagata_22ind,template_ivacode_pagata_22ind,template_impcode_pagata_22ind,template_ivacode_pagata_22ind,1,1,False,-1,-1
 22I5,22I5,l10n_it_chart_template_generic,IVA al 22% detraibile al 50%,35,0.22,,True,percent,,,purchase,template_impcode_pagata_22det50,,template_impcode_pagata_22det50,,1,1,False,-1,-1
 22I5b,22I5b,l10n_it_chart_template_generic,IVA al 22% detraibile al 50% (D),200,0,22I5,False,balance,1601,1601,purchase,,template_ivacode_pagata_22det50,,template_ivacode_pagata_22det50,1,1,False,-1,-1
 22I5a,22I5a,l10n_it_chart_template_generic,IVA al 22% detraibile al 50% (I),100,0.5,22I5,False,percent,,,purchase,,template_ivacode_pagata_22det50ind,,template_ivacode_pagata_22det50ind,1,1,False,-1,-1
index 1599d3d..9b3dd50 100644 (file)
         <record id="btw_code_chart_root" model="account.tax.code.template">
             <field name="name">Nederlands BTW Plan</field>
             <field name="parent_id" eval="False"/>
+            <field name="sequence" eval="1"/>
         </record>
 <!-- De gegevens voor de BTW (gegevens voor omzetbedragen staan verderop) -->
         <record id="btw_totaal" model="account.tax.code.template">
             <field name="code">B</field>
             <field name="name">Gegevens omzetbelasting (BTW)</field>
             <field name="parent_id" ref="btw_code_chart_root"/>
+            <field name="sequence" eval="5"/>
         </record>
 <!-- 1 Leveringen en/of diensten binnenland -->
         <record id="btw_code_binnenland" model="account.tax.code.template">
             <field name="code">1</field>
             <field name="name">Leveringen en/of diensten binnenland (BTW)</field>
             <field name="parent_id" ref="btw_totaal"/>
+            <field name="sequence" eval="10"/>
         </record>
         <record id="btw_code_1a" model="account.tax.code.template">
             <field name="code">1a</field>
             <field name="parent_id" ref="btw_code_binnenland"/>
             <field name="name">Leveringen/diensten belast met 21% (BTW)</field>
+            <field name="sequence" eval="11"/>
         </record>
         <record id="btw_code_1b" model="account.tax.code.template">
             <field name="code">1b</field>
             <field name="parent_id" ref="btw_code_binnenland"/>
             <field name="name">Leveringen/diensten belast met 6% (BTW)</field>
+            <field name="sequence" eval="12"/>
         </record>
         <record id="btw_code_1c" model="account.tax.code.template">
             <field name="code">1c</field>
             <field name="parent_id" ref="btw_code_binnenland"/>
             <field name="name">Leveringen/diensten belast met overige tarieven behalve 0% (BTW)</field>
+            <field name="sequence" eval="13"/>
         </record>
         <record id="btw_code_1d" model="account.tax.code.template">
             <field name="code">1d</field>
             <field name="parent_id" ref="btw_code_binnenland"/>
             <field name="name">Prive-gebruik (BTW)</field>
+            <field name="sequence" eval="14"/>
         </record>
         <record id="btw_code_1e" model="account.tax.code.template">
             <field name="code">1e</field>
             <field name="parent_id" ref="btw_code_binnenland"/>
             <field name="name">Leveringen/diensten belast met 0% of niet bij u belast (BTW)</field>
+            <field name="sequence" eval="15"/>
         </record>
 <!-- 2. Verleggingsregeling -->
         <record id="btw_code_verlegging" model="account.tax.code.template">
             <field name="code">2</field>
             <field name="name">Verleggingsregelingen: BTW naar u verlegd (BTW)</field>
             <field name="parent_id" ref="btw_totaal"/>
+            <field name="sequence" eval="20"/>
         </record>
         <record id="btw_code_2a" model="account.tax.code.template">
             <field name="code">2a</field>
             <field name="parent_id" ref="btw_code_verlegging"/>
             <field name="name">Heffing van omzetbelasting is naar u verlegd (BTW)</field>
+            <field name="sequence" eval="21"/>
         </record>
 <!-- Voor rubriek 3 hoeft geen omzetbelasting aangegeven te worden, wel het omzet bedrag, zie hiervoor verderop
      3. Leveringen naar het buitenland -->
             <field name="code">4</field>
             <field name="name">Leveringen vanuit het buitenland (BTW)</field>
             <field name="parent_id" ref="btw_totaal"/>
+            <field name="sequence" eval="40"/>
         </record>
         <record id="btw_code_4a" model="account.tax.code.template">
             <field name="code">4a</field>
             <field name="parent_id" ref="btw_code_vanuit_buitenland"/>
             <field name="name">Leveringen uit landen buiten de EU (invoer) (BTW)</field>
             <field name="sign" eval="-1" />
+            <field name="sequence" eval="41"/>
         </record>
         <record id="btw_code_4b" model="account.tax.code.template">
             <field name="code">4b</field>
             <field name="parent_id" ref="btw_code_vanuit_buitenland"/>
             <field name="name">Verwerving van goederen uit landen binnen de EU (BTW)</field>
             <field name="sign" eval="-1" />
+            <field name="sequence" eval="42"/>
         </record>
 <!-- 5. Voorbelasting, kleineondernemersregeling, schatting en eindtotaal -->
         <record id="btw_code_voorbelasting" model="account.tax.code.template">
             <field name="code">5</field>
             <field name="name">Voorbelasting, kleineondernemersregeling, schatting en eindtotaal (BTW)</field>
             <field name="parent_id" ref="btw_totaal"/>
+            <field name="sequence" eval="50"/>
         </record>
         <record id="btw_code_5a" model="account.tax.code.template">
             <field name="code">5a</field>
             <field name="parent_id" ref="btw_code_voorbelasting"/>
             <field name="name">Verschuldigde omzetbelasting (rubrieken 1a t/m 4b) (BTW)</field>
+            <field name="sequence" eval="51"/>
         </record>
         <record id="btw_code_5b" model="account.tax.code.template">
             <field name="code">5b</field>
         <field eval="-1" name="sign"/>
             <field name="parent_id" ref="btw_code_voorbelasting"/>
             <field name="name">Voorbelasting (BTW)</field>
+            <field name="sequence" eval="52"/>
         </record>
         <record id="btw_code_5c" model="account.tax.code.template">
             <field name="code">5c</field>
             <field name="parent_id" ref="btw_code_voorbelasting"/>
             <field name="name">Subtotaal (rubriek 5a min 5b) (BTW)</field>
+            <field name="sequence" eval="53"/>
         </record>
         <record id="btw_code_5d" model="account.tax.code.template">
             <field name="code">5d</field>
             <field name="parent_id" ref="btw_code_voorbelasting"/>
             <field name="name">Vermindering volgens de kleineondernemersregeling (BTW)</field>
+            <field name="sequence" eval="54"/>
         </record>
         <record id="btw_code_5e" model="account.tax.code.template">
             <field name="code">5e</field>
             <field name="parent_id" ref="btw_code_voorbelasting"/>
             <field name="name">Schatting vorige aangifte(n) (BTW)</field>
+            <field name="sequence" eval="55"/>
         </record>
         <record id="btw_code_5f" model="account.tax.code.template">
             <field name="code">5f</field>
             <field name="parent_id" ref="btw_code_voorbelasting"/>
             <field name="name">Schatting deze aangifte (BTW)</field>
+            <field name="sequence" eval="56"/>
         </record>
         <record id="btw_code_5g" model="account.tax.code.template">
             <field name="code">5g</field>
             <field name="parent_id" ref="btw_code_voorbelasting"/>
             <field name="name">Totaal te betalen/terug te vragen (BTW)</field>
+            <field name="sequence" eval="57"/>
         </record>
         
 <!-- De gegevens over de omzet -->
             <field name="code">A</field>
             <field name="name">Gegevens omzetbedragen</field>
             <field name="parent_id" ref="btw_code_chart_root"/>
+            <field name="sequence" eval="1"/>
         </record>
 <!-- 1 Leveringen en/of diensten binnenland -->
         <record id="omz_code_binnenland" model="account.tax.code.template">
             <field name="code">1</field>
             <field name="name">Leveringen en/of diensten binnenland (omzet)</field>
             <field name="parent_id" ref="omz_totaal"/>
+            <field name="sequence" eval="10"/>
         </record>
         <record id="omz_code_1a" model="account.tax.code.template">
             <field name="code">1a</field>
             <field name="parent_id" ref="omz_code_binnenland"/>
             <field name="name">Leveringen/diensten belast met 21% (omzet)</field>
+            <field name="sequence" eval="11"/>
         </record>
         <record id="omz_code_1b" model="account.tax.code.template">
             <field name="code">1b</field>
             <field name="parent_id" ref="omz_code_binnenland"/>
             <field name="name">Leveringen/diensten belast met 6% (omzet)</field>
+            <field name="sequence" eval="12"/>
         </record>
         <record id="omz_code_1c" model="account.tax.code.template">
             <field name="code">1c</field>
             <field name="parent_id" ref="omz_code_binnenland"/>
             <field name="name">Leveringen/diensten belast met overige tarieven behalve 0% (omzet)</field>
+            <field name="sequence" eval="13"/>
         </record>
         <record id="omz_code_1d" model="account.tax.code.template">
             <field name="code">1d</field>
             <field name="parent_id" ref="omz_code_binnenland"/>
             <field name="name">Prive-gebruik (omzet)</field>
+            <field name="sequence" eval="14"/>
         </record>
         <record id="omz_code_1e" model="account.tax.code.template">
             <field name="code">1e</field>
             <field name="parent_id" ref="omz_code_binnenland"/>
             <field name="name">Leveringen/diensten belast met 0% of niet bij u belast (omzet)</field>
+            <field name="sequence" eval="15"/>
         </record>
 <!-- 2. Verleggingsregeling -->
         <record id="omz_code_verlegging" model="account.tax.code.template">
             <field name="code">2</field>
             <field name="name">Verleggingsregelingen: BTW naar u verlegd (omzet)</field>
             <field name="parent_id" ref="omz_totaal"/>
+            <field name="sequence" eval="20"/>
         </record>
         <record id="omz_code_2a" model="account.tax.code.template">
             <field name="code">2a</field>
             <field name="parent_id" ref="omz_code_verlegging"/>
             <field name="name">Heffing van omzetbelasting is naar u verlegd (omzet)</field>
+            <field name="sequence" eval="21"/>
         </record>
 <!-- 3. Leveringen naar het buitenland -->
         <record id="omz_code_naar_buitenland" model="account.tax.code.template">
             <field name="code">3</field>
             <field name="name">Leveringen naar het buitenland (omzet)</field>
             <field name="parent_id" ref="omz_totaal"/>
+            <field name="sequence" eval="30"/>
         </record>
         <record id="omz_code_3a" model="account.tax.code.template">
             <field name="code">3a</field>
             <field name="parent_id" ref="omz_code_naar_buitenland"/>
             <field name="name">Leveringen naar landen buiten de EU (uitvoer) (omzet)</field>
+            <field name="sequence" eval="31"/>
         </record>
         <record id="omz_code_3b" model="account.tax.code.template">
             <field name="code">3b</field>
             <field name="parent_id" ref="omz_code_naar_buitenland"/>
             <field name="name">Leveringen naar landen binnen de EU (omzet)</field>
+            <field name="sequence" eval="32"/>
         </record>
         <record id="omz_code_3c" model="account.tax.code.template">
             <field name="code">3c</field>
             <field name="parent_id" ref="omz_code_naar_buitenland"/>
             <field name="name">Installatie/afstandsverkopen binnen de EU (omzet)</field>
+            <field name="sequence" eval="33"/>
         </record>
 <!-- 4. Leveringen vanuit het buitenland -->
         <record id="omz_code_vanuit_buitenland" model="account.tax.code.template">
             <field name="code">4</field>
             <field name="name">Leveringen vanuit het buitenland (omzet)</field>
             <field name="parent_id" ref="omz_totaal"/>
+            <field name="sequence" eval="40"/>
         </record>
         <record id="omz_code_4a" model="account.tax.code.template">
             <field name="code">4a</field>
             <field name="parent_id" ref="omz_code_vanuit_buitenland"/>
             <field name="name">Leveringen uit landen buiten de EU (invoer) (omzet)</field>
+            <field name="sequence" eval="41"/>
         </record>
         <record id="omz_code_4b" model="account.tax.code.template">
             <field name="code">4b</field>
             <field name="parent_id" ref="omz_code_vanuit_buitenland"/>
             <field name="name">Verwerving van goederen uit landen binnen de EU (omzet)</field>
+            <field name="sequence" eval="42"/>
         </record>
         
             <!-- Chart template -->
index c193f42..2357f64 100644 (file)
         <field name="model">account.chart.template</field>
         <field name="key">default</field>
         <field name="res_id" ref="pl_chart_template"/>
-        <field name="value" ref="base.PLZ"/>
+        <field name="value" ref="base.PLN"/>
     </record>
     
 </data>
index e82ed94..7df645c 100644 (file)
@@ -482,9 +482,9 @@ class mail_thread(osv.AbstractModel):
         partner_ids = []
         s = ', '.join([decode(message.get(h)) for h in header_fields if message.get(h)])
         for email_address in tools.email_split(s):
-            related_partners = partner_obj.search(cr, uid, [('email', 'ilike', email_address), ('user_ids', '!=', False)], limit=1, context=context)
+            related_partners = partner_obj.search(cr, uid, [('email', '=ilike', email_address), ('user_ids', '!=', False)], limit=1, context=context)
             if not related_partners:
-                related_partners = partner_obj.search(cr, uid, [('email', 'ilike', email_address)], limit=1, context=context)
+                related_partners = partner_obj.search(cr, uid, [('email', '=ilike', email_address)], limit=1, context=context)
             partner_ids += related_partners
         return partner_ids
 
index 45b2bcb..3b03146 100644 (file)
@@ -247,19 +247,17 @@ class mrp_bom(osv.osv):
         return True
 
     def _check_product(self, cr, uid, ids, context=None):
-        all_prod = []
         boms = self.browse(cr, uid, ids, context=context)
-        def check_bom(boms):
+        def check_bom(boms, all_prod):
             res = True
             for bom in boms:
                 if bom.product_id.id in all_prod:
-                    res = res and False
-                all_prod.append(bom.product_id.id)
+                    return False
                 lines = bom.bom_lines
                 if lines:
-                    res = res and check_bom([bom_id for bom_id in lines if bom_id not in boms])
+                    res = res and check_bom([bom_id for bom_id in lines if bom_id not in boms], all_prod + [bom.product_id.id])
             return res
-        return check_bom(boms)
+        return check_bom(boms, [])
 
     _constraints = [
         (_check_recursion, 'Error ! You cannot create recursive BoM.', ['parent_id']),
@@ -313,7 +311,7 @@ class mrp_bom(osv.osv):
                 max_prop = prop
         return result
 
-    def _bom_explode(self, cr, uid, bom, factor, properties=None, addthis=False, level=0, routing_id=False):
+    def _bom_explode(self, cr, uid, bom, factor, properties=None, addthis=False, level=0, routing_id=False, context=None):
         """ Finds Products and Work Centers for related BoM for manufacturing order.
         @param bom: BoM of particular product.
         @param factor: Factor of product UoM.
@@ -335,7 +333,7 @@ class mrp_bom(osv.osv):
             newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
 
             if newbom:
-                res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
+                res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10, context=context)
                 result = result + res[0]
                 result2 = result2 + res[1]
                 phantom = True
@@ -367,7 +365,7 @@ class mrp_bom(osv.osv):
                         'hour': float(wc_use.hour_nbr*mult + ((wc.time_start or 0.0)+(wc.time_stop or 0.0)+cycle*(wc.time_cycle or 0.0)) * (wc.time_efficiency or 1.0)),
                     })
             for bom2 in bom.bom_lines:
-                res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
+                res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10, context=context)
                 result = result + res[0]
                 result2 = result2 + res[1]
         return result, result2
@@ -638,7 +636,7 @@ class mrp_production(osv.osv):
     
             # get components and workcenter_lines from BoM structure
             factor = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, bom_point.product_uom.id)
-            res = bom_obj._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties, routing_id=production.routing_id.id)
+            res = bom_obj._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties, routing_id=production.routing_id.id, context=context)
             results = res[0] # product_lines
             results2 = res[1] # workcenter_lines
     
index 7e072d9..4c2e86c 100644 (file)
             <field name="field_parent">child_complete_ids</field>
             <field name="arch" type="xml">
                 <tree string="BoM Structure" colors="blue:method">
-                    <field name="sequence" invisible="1"/>
                     <field name="name" groups="base.group_no_one"/>
                     <field name="code"/>
                     <field name="product_id"/>
index 9e5dc91..b513940 100644 (file)
@@ -45,7 +45,7 @@
         <field name="name">mrp_bom multi-company</field>
         <field name="model_id" search="[('model','=','mrp.bom')]" model="ir.model"/>
         <field name="global" eval="True"/>
-        <field name="domain_force">['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]</field>
+        <field name="domain_force">['|','|',('company_id.child_ids','child_of',[user.company_id.id]),('company_id','child_of',[user.company_id.id]),('company_id','=',False)]</field>
     </record>
 
     <record model="ir.rule" id="mrp_routing_rule">
index dc434ca..20fdde6 100644 (file)
@@ -40,7 +40,7 @@
 -
   !python {model: mrp.production}: |
     order = self.browse(cr, uid, ref("mrp_production_servicetype_mo1"))
-    assert order.state == 'confirmed', "Production order should be confirmed."
+    assert order.state == 'ready', "Production order should be ready."
     for move_line in order.move_lines:
         move_line.action_consume(move_line.product_qty)
 -
diff --git a/addons/mrp/test/multicompany.yml b/addons/mrp/test/multicompany.yml
new file mode 100644 (file)
index 0000000..7ed9cf8
--- /dev/null
@@ -0,0 +1,20 @@
+-
+  Set the current user as multicompany user
+-
+  !context
+    uid: 'stock.multicompany_user'
+
+-
+  check no error on getting default mrp.production values in multicompany setting
+-
+  !python {model: mrp.production}: |
+    location_obj = self.pool.get('stock.location')
+    fields = ['location_src_id', 'location_dest_id']
+    defaults = self.default_get(cr, uid, ['location_id', 'location_dest_id', 'type'], context)
+    log('got defaults: %s', defaults)
+    for field in fields:
+        if defaults.get(field):
+            try:
+                location_obj.check_access_rule(cr, uid, [defaults[field]], 'read', context)
+            except Exception, exc:
+                assert False, "unreadable location %s: %s" % (field, exc)
index 605e80e..b087f7b 100644 (file)
@@ -35,7 +35,7 @@ class mrp_subproduct(osv.osv):
   'Fixed' depicts a situation where the quantity of created byproduct is always equal to the quantity set on the BoM, regardless of how many are created in the production order.\
   By opposition, 'Variable' means that the quantity will be computed as\
     '(quantity of byproduct set on the BoM / quantity of manufactured product set on the BoM * quantity of manufactured product in the production order.)'"),
-        'bom_id': fields.many2one('mrp.bom', 'BoM'),
+        'bom_id': fields.many2one('mrp.bom', 'BoM', ondelete='cascade'),
     }
     _defaults={
         'subproduct_type': 'variable',
index 450608a..a76f4cf 100644 (file)
@@ -104,7 +104,7 @@ class procurement_order(osv.osv):
             readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
             " a make to order method."),
         'note': fields.text('Note'),
-        'message': fields.char('Latest error', size=124, help="Exception occurred while computing procurement orders."),
+        'message': fields.char('Latest error', help="Exception occurred while computing procurement orders."),
         'state': fields.selection([
             ('draft','Draft'),
             ('cancel','Cancelled'),
index 5e9f8cf..e5fd333 100644 (file)
@@ -83,6 +83,7 @@ class product_pricelist(report_sxw.rml_parse):
         cat_ids=[]
         res=[]
         self.pricelist = form['price_list']
+        self.partner_id = form['partner_id']
         self._set_quantity(form)
         pool = pooler.get_pool(self.cr.dbname)
         pro_ids=[]
@@ -108,17 +109,18 @@ class product_pricelist(report_sxw.rml_parse):
                     if qty == 0:
                         val['qty'+str(i)] = 0.0
                     else:
-                        val['qty'+str(i)]=self._get_price(self.pricelist, product['id'], qty)
+                        val['qty'+str(i)]=self._get_price(self.pricelist, product['id'], qty, partner_id=self.partner_id)
                     i += 1
                 products.append(val)
             res.append({'name':cat[1],'products': products})
         return res
 
-    def _get_price(self, pricelist_id, product_id, qty):
+    def _get_price(self, pricelist_id, product_id, qty, partner_id=None):
         sale_price_digits = self.get_digits(dp='Product Price')
         pool = pooler.get_pool(self.cr.dbname)
         pricelist = self.pool.get('product.pricelist').browse(self.cr, self.uid, [pricelist_id], context=self.localcontext)[0]
-        price_dict = pool.get('product.pricelist').price_get(self.cr, self.uid, [pricelist_id], product_id, qty, context=self.localcontext)
+        price_dict = pool.get('product.pricelist').price_get(self.cr, self.uid, [pricelist_id], product_id, qty, partner=partner_id, context=self.localcontext)
+
         if price_dict[pricelist_id]:
             price = self.formatLang(price_dict[pricelist_id], digits=sale_price_digits, currency_obj=pricelist.currency_id)
         else:
index 8989e97..d145670 100644 (file)
@@ -28,6 +28,9 @@ class product_price_list(osv.osv_memory):
     _description = 'Price List'
 
     _columns = {
+        'partner_id': fields.many2one('res.partner', 'Supplier',
+            help='For price lists based on the supplier price, fill in the '
+            'supplier in question here'),
         'price_list': fields.many2one('product.pricelist', 'PriceList', required=True),
         'qty1': fields.integer('Quantity-1'),
         'qty2': fields.integer('Quantity-2'),
@@ -51,9 +54,10 @@ class product_price_list(osv.osv_memory):
         if context is None:
             context = {}
         datas = {'ids': context.get('active_ids', [])}
-        res = self.read(cr, uid, ids, ['price_list','qty1', 'qty2','qty3','qty4','qty5'], context=context)
+        res = self.read(cr, uid, ids, ['price_list','qty1', 'qty2','qty3','qty4','qty5', 'partner_id'], context=context)
         res = res and res[0] or {}
         res['price_list'] = res['price_list'][0]
+        res['partner_id'] = res['partner_id'] and res['partner_id'][0]
         datas['form'] = res
         return {
             'type': 'ir.actions.report.xml',
index 2de5859..1c23964 100644 (file)
@@ -16,6 +16,7 @@
                         <field name="qty3"/>
                         <field name="qty4"/>
                         <field name="qty5"/>
+                        <field name="partner_id" />
                     </group>
                     <footer>
                         <button name="print_report" string="Print"  type="object" class="oe_highlight"  />
index fb4aac9..92f8e4d 100644 (file)
                 </form>
             </field>
         </record>
+        <record id="product_manufacturer_search_form_view" model="ir.ui.view">
+            <field name="name">product.manufacturer.search.form</field>
+            <field name="model">product.product</field>
+            <field name="inherit_id" ref="product.product_search_form_view"/>
+            <field name="arch" type="xml">
+                <field name="name" position="after">
+                    <field name="manufacturer" string="Manufacturer" filter_domain="['|', ('manufacturer', 'ilike', self), ('manufacturer_pname', 'ilike', self)]"/>
+                </field>
+            </field>
+        </record>
 
     </data>
 </openerp>
index c0a516c..cb8c64e 100644 (file)
@@ -82,6 +82,7 @@ Dashboard / Reports for Project Management will include:
         'test/project_demo.yml',
         'test/project_process.yml',
         'test/task_process.yml',
+        'test/hours_process.yml',
     ],
     'installable': True,
     'auto_install': False,
index f62774f..16acc6d 100644 (file)
@@ -135,6 +135,7 @@ class project(osv.osv):
             res.update(ids)
         return list(res)
 
+    # Deprecated; the _progress_rate method does not use this anymore
     def _get_project_and_children(self, cr, uid, ids, context=None):
         """ retrieve all children projects of project ids;
             return a dictionary mapping each project to its parent project (or None)
@@ -154,28 +155,75 @@ class project(osv.osv):
         return res
 
     def _progress_rate(self, cr, uid, ids, names, arg, context=None):
-        child_parent = self._get_project_and_children(cr, uid, ids, context)
         # compute planned_hours, total_hours, effective_hours specific to each project
+        # How this works: the WITH RECURSIVE statement will create an union line
+        # for each parent project with the hours of each child project; the final
+        # SUM(...) ensures we get a total of hours by project.
         cr.execute("""
-            SELECT project_id, COALESCE(SUM(planned_hours), 0.0),
-                COALESCE(SUM(total_hours), 0.0), COALESCE(SUM(effective_hours), 0.0)
-            FROM project_task WHERE project_id IN %s AND state <> 'cancelled'
-            GROUP BY project_id
-            """, (tuple(child_parent.keys()),))
+        WITH RECURSIVE recur_table(project_id,
+                                   parent_id,
+                                   planned_hours,
+                                   total_hours,
+                                   effective_hours) AS (
+            SELECT project.id,
+                   parent.id,
+                   COALESCE(task.planned, 0.0),
+                   COALESCE(task.total, 0.0),
+                   COALESCE(task.effective, 0.0)
+            FROM project_project project
+                 LEFT JOIN account_analytic_account account
+                      ON project.analytic_account_id = account.id
+                 LEFT JOIN project_project parent
+                      ON parent.analytic_account_id = account.parent_id
+                 LEFT JOIN (SELECT project_id,
+                                   SUM(planned_hours) as planned,
+                                   SUM(total_hours) as total,
+                                   SUM(effective_hours) as effective
+                            FROM project_task
+                            WHERE state <> 'cancelled'
+                            GROUP BY project_id) AS task
+                      ON project.id = task.project_id
+        UNION ALL
+            SELECT project.id,
+                   parent.id,
+                   recur_table.planned_hours,
+                   recur_table.total_hours,
+                   recur_table.effective_hours
+            FROM project_project project
+                 LEFT JOIN account_analytic_account account
+                      ON project.analytic_account_id = account.id
+                 LEFT JOIN project_project parent
+                      ON parent.analytic_account_id = account.parent_id
+                 LEFT JOIN (SELECT project_id,
+                                   SUM(planned_hours) as planned,
+                                   SUM(total_hours) as total,
+                                   SUM(effective_hours) as effective
+                            FROM project_task
+                            WHERE state <> 'cancelled'
+                            GROUP BY project_id) AS task
+                      ON project.id = task.project_id
+                 JOIN recur_table ON project.id = recur_table.parent_id
+        )
+        SELECT project_id,
+               SUM(planned_hours),
+               SUM(total_hours),
+               SUM(effective_hours)
+        FROM recur_table
+        WHERE project_id IN %s
+        GROUP BY project_id
+            """, (tuple(ids),))
         # aggregate results into res
-        res = dict([(id, {'planned_hours':0.0,'total_hours':0.0,'effective_hours':0.0}) for id in ids])
-        for id, planned, total, effective in cr.fetchall():
-            # add the values specific to id to all parent projects of id in the result
-            while id:
-                if id in ids:
-                    res[id]['planned_hours'] += planned
-                    res[id]['total_hours'] += total
-                    res[id]['effective_hours'] += effective
-                id = child_parent[id]
+        res = dict([(result_line[0], {'planned_hours': result_line[1],
+                                      'total_hours': result_line[2],
+                                      'effective_hours': result_line[3]})
+                    for result_line in cr.fetchall()])
         # compute progress rates
         for id in ids:
             if res[id]['total_hours']:
-                res[id]['progress_rate'] = round(100.0 * res[id]['effective_hours'] / res[id]['total_hours'], 2)
+                res[id]['progress_rate'] = round(100.0 *
+                                                 res[id]['effective_hours'] /
+                                                 res[id]['total_hours'],
+                                                 2)
             else:
                 res[id]['progress_rate'] = 0.0
         return res
@@ -192,7 +240,7 @@ class project(osv.osv):
         res =  super(project, self).unlink(cr, uid, ids, context=context)
         mail_alias.unlink(cr, uid, alias_ids, context=context)
         return res
-    
+
     def _get_attached_docs(self, cr, uid, ids, field_name, arg, context):
         res = {}
         attachment = self.pool.get('ir.attachment')
@@ -203,7 +251,7 @@ class project(osv.osv):
             task_attachments = attachment.search(cr, uid, [('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)], context=context, count=True)
             res[id] = (project_attachments or 0) + (task_attachments or 0)
         return res
-        
+
     def _task_count(self, cr, uid, ids, field_name, arg, context=None):
         if context is None:
             context = {}
@@ -228,7 +276,7 @@ class project(osv.osv):
     def attachment_tree_view(self, cr, uid, ids, context):
         task_ids = self.pool.get('project.task').search(cr, uid, [('project_id', 'in', ids)])
         domain = [
-             '|', 
+             '|',
              '&', ('res_model', '=', 'project.project'), ('res_id', 'in', ids),
              '&', ('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)
                ]
@@ -733,7 +781,7 @@ class task(base_stage, osv.osv):
                 new_name = _("%s (copy)") % (default.get('name', ''))
                 default.update({'name':new_name})
         return super(task, self).copy_data(cr, uid, id, default, context)
-    
+
     def copy(self, cr, uid, id, default=None, context=None):
         if context is None:
             context = {}
diff --git a/addons/project/test/hours_process.yml b/addons/project/test/hours_process.yml
new file mode 100644 (file)
index 0000000..6b75fcb
--- /dev/null
@@ -0,0 +1,174 @@
+-
+  First, create the analytic accounts
+-
+  !record {model: account.analytic.account, id: parent_project_account}:
+    name: Parent Account
+    code: PAR
+    type: view
+-
+  !record {model: account.analytic.account, id: child1_project_account}:
+    name: Child Account 1
+    code: C1
+    type: view
+-
+  I create a main project
+-
+  !record {model: project.project, id: project_project_parent}:
+    company_id: base.main_company
+    name: Parent Project
+    user_id: base.user_demo
+    parent_id: all_projects_account
+    analytic_account_id: parent_project_account
+    alias_model: project.task
+-
+  A first child project
+-
+  !record {model: project.project, id: project_project_child_1}:
+    company_id: base.main_company
+    name: Child Project 1
+    user_id: base.user_demo
+    parent_id: parent_project_account
+    analytic_account_id: child1_project_account
+    alias_model: project.task
+-
+  A second child project
+-
+  !record {model: project.project, id: project_project_child_2}:
+    company_id: base.main_company
+    name: Child Project 2
+    user_id: base.user_demo
+    parent_id: parent_project_account
+    alias_model: project.task
+-
+  A child of the first child project
+-
+  !record {model: project.project, id: project_project_child_1_1}:
+    company_id: base.main_company
+    name: Child Project 1 1
+    user_id: base.user_demo
+    parent_id: child1_project_account
+    alias_model: project.task
+-
+  A task for the main project, with 20 hours planned and 20 hours remaining
+-
+  !record {model: project.task, id: project_task_parent_project}:
+    date_start: !eval time.strftime('%Y-05-%d %H:%M:%S')
+    name: Task parent project
+    planned_hours: 20.0
+    remaining_hours: 20.0
+    project_id: project_project_parent
+    user_id: base.user_demo
+-
+  I test the hours
+-
+  !python {model: project.project}: |
+    project = self.browse(cr, uid, ref("project_project_parent"))
+    assert abs(project.planned_hours - 20.0) < 1e-4, "Planned hours are not correct! 20.0 != %s" % project.planned_hours
+    assert abs(project.total_hours - 20.0) < 1e-4, "Total hours are not correct! 20.0 != %s" % project.total_hours
+    assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
+    assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
+-
+  A task for the first child project, with 30 hours planned and 30 hours remaining
+-
+  !record {model: project.task, id: project_task_child_project_1}:
+    date_start: !eval time.strftime('%Y-05-%d %H:%M:%S')
+    name: Task child project 1
+    planned_hours: 30.0
+    remaining_hours: 30.0
+    project_id: project_project_child_1
+    user_id: base.user_demo
+-
+  I test the hours
+-
+  !python {model: project.project}: |
+    project = self.browse(cr, uid, ref("project_project_parent"))
+    assert abs(project.planned_hours - 50.0) < 1e-4, "Planned hours are not correct! 50.0 != %s" % project.planned_hours
+    assert abs(project.total_hours - 50.0) < 1e-4, "Total hours are not correct! 50.0 != %s" % project.total_hours
+    assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
+    assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
+    project = self.browse(cr, uid, ref("project_project_child_1"))
+    assert abs(project.planned_hours - 30.0) < 1e-4, "Planned hours are not correct! 30.0 != %s" % project.planned_hours
+    assert abs(project.total_hours - 30.0) < 1e-4, "Total hours are not correct! 30.0 != %s" % project.total_hours
+    assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
+    assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
+-
+  A task for the second child project, with 40 hours planned and 10 hours remaining
+-
+  !record {model: project.task, id: project_task_child_project_2}:
+    date_start: !eval time.strftime('%Y-05-%d %H:%M:%S')
+    name: Task child project 2
+    planned_hours: 40.0
+    remaining_hours: 10.0
+    project_id: project_project_child_2
+    user_id: base.user_demo
+-
+  I test the hours
+-
+  !python {model: project.project}: |
+    project = self.browse(cr, uid, ref("project_project_parent"))
+    assert abs(project.planned_hours - 90.0) < 1e-4, "Planned hours are not correct! 90.0 != %s" % project.planned_hours
+    assert abs(project.total_hours - 60.0) < 1e-4, "Total hours are not correct! 60.0 != %s" % project.total_hours
+    assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
+    assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
+    project = self.browse(cr, uid, ref("project_project_child_2"))
+    assert abs(project.planned_hours - 40.0) < 1e-4, "Planned hours are not correct! 40.0 != %s" % project.planned_hours
+    assert abs(project.total_hours - 10.0) < 1e-4, "Total hours are not correct! 10.0 != %s" % project.total_hours
+    assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
+    assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rat
+-
+  A task for the child child project, with 50 hours planned and 50 hours remaining
+-
+  !record {model: project.task, id: project_task_child_project_1_1}:
+    date_start: !eval time.strftime('%Y-05-%d %H:%M:%S')
+    name: Task child project 1 1
+    planned_hours: 50.0
+    remaining_hours: 50.0
+    project_id: project_project_child_1_1
+    user_id: base.user_demo
+-
+  I test the hours
+-
+  !python {model: project.project}: |
+    project = self.browse(cr, uid, ref("project_project_parent"))
+    assert abs(project.planned_hours - 140.0) < 1e-4, "Planned hours are not correct! 140.0 != %s" % project.planned_hours
+    assert abs(project.total_hours - 110.0) < 1e-4, "Total hours are not correct! 110.0 != %s" % project.total_hours
+    assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
+    assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
+    project = self.browse(cr, uid, ref("project_project_child_1"))
+    assert abs(project.planned_hours - 80.0) < 1e-4, "Planned hours are not correct! 80.0 != %s" % project.planned_hours
+    assert abs(project.total_hours - 80.0) < 1e-4, "Total hours are not correct! 80.0 != %s" % project.total_hours
+    assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
+    assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
+    project = self.browse(cr, uid, ref("project_project_child_1_1"))
+    assert abs(project.planned_hours - 50.0) < 1e-4, "Planned hours are not correct! 50.0 != %s" % project.planned_hours
+    assert abs(project.total_hours - 50.0) < 1e-4, "Total hours are not correct! 50.0 != %s" % project.total_hours
+    assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
+    assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
+-
+  I create a task work on child 1 task
+-
+  !record {model: project.task.work, id: project_child_1_work}:
+    name: test work
+    hours: 10.0
+    task_id: project_task_child_project_1
+    user_id: base.user_demo
+-
+  and refresh the task to account for it
+-
+  !record {model: project.task, id: project_task_child_project_1}:
+    planned_hours: 20.0
+-
+  I test the hours
+-
+  !python {model: project.project}: |
+    project = self.browse(cr, uid, ref("project_project_parent"))
+    assert abs(project.planned_hours - 130.0) < 1e-4, "Planned hours are not correct! 130.0 != %s" % project.planned_hours
+    assert abs(project.total_hours - 110.0) < 1e-4, "Total hours are not correct! 110.0 != %s" % project.total_hours
+    assert abs(project.effective_hours - 10.0) < 1e-4, "Effective hours are not correct! 10.0 != %s" % project.effective_hours
+    assert abs(project.progress_rate - 9.09) < 1e-4, "Progress rate is not correct! 9.09 != %s" % project.progress_rate
+    project = self.browse(cr, uid, ref("project_project_child_1"))
+    assert abs(project.planned_hours - 70.0) < 1e-4, "Planned hours are not correct! 70.0 != %s" % project.planned_hours
+    assert abs(project.total_hours - 80.0) < 1e-4, "Total hours are not correct! 80.0 != %s" % project.total_hours
+    assert abs(project.effective_hours - 10.0) < 1e-4, "Effective hours are not correct! 10.0 != %s" % project.effective_hours
+    assert abs(project.progress_rate - 12.5) < 1e-4, "Progress rate is not correct! 12.5 != %s" % project.progress_rate
+
index efb4e4e..a59ef47 100644 (file)
@@ -549,7 +549,7 @@ class project_issue(base_stage, osv.osv):
         
         res = super(project_issue, self).message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, content_subtype=content_subtype, **kwargs)
         
-        if thread_id:
+        if thread_id and subtype:
             self.write(cr, uid, thread_id, {'date_action_last': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)    
         
         return res   
index 3b5bbae..18cef11 100644 (file)
@@ -276,7 +276,7 @@ class task(osv.osv):
                         if vals.get('project_id',False):
                             vals_line['account_id'] = acc_id
                         if vals.get('name',False):
-                            vals_line['name'] = '%s: %s' % (tools.ustr(vals['name']), tools.ustr(task_work.name) or '/')
+                            vals_line['name'] = '%s: %s' % (tools.ustr(vals['name']), tools.ustr(task_work.name or '/'))
                         hr_anlytic_timesheet.write(cr, uid, [line_id], vals_line, {})
 
         res = super(task,self).write(cr, uid, ids, vals, context)
index e6f4989..1594b7f 100644 (file)
@@ -802,7 +802,8 @@ class purchase_order(osv.osv):
                 if porder.notes:
                     order_infos['notes'] = (order_infos['notes'] or '') + ('\n%s' % (porder.notes,))
                 if porder.origin:
-                    order_infos['origin'] = (order_infos['origin'] or '') + ' ' + porder.origin
+                    if not porder.origin in order_infos['origin'] and not order_infos['origin'] in porder.origin:
+                        order_infos['origin'] = (order_infos['origin'] or '') + ' ' + porder.origin
 
             for order_line in porder.order_line:
                 line_key = make_key(order_line, ('name', 'date_planned', 'taxes_id', 'price_unit', 'product_id', 'move_dest_id', 'account_analytic_id'))
index f8fdcb0..a28a251 100644 (file)
             <field name="arch" type="xml">
                  <xpath expr="//div[contains(@class, 'oe_title')]" position="before">
                     <div class="oe_right oe_button_box" name="buttons">
+                        <field name="picking_ids" invisible="1"/>
                         <button type="object"
                             name="view_picking"
-                            string="Incoming Shipments" states="approved"/>
+                            string="Incoming Shipments"
+                            attrs="{'invisible': ['|',('picking_ids','=',False),('picking_ids','=',[])]}"/>
                         <button type="object"  name="invoice_open"
                             string="Invoices" attrs="{'invisible': [('state', '=', 'draft')]}"/> 
                     </div>
index 031c902..479e2db 100644 (file)
@@ -65,16 +65,47 @@ class purchase_requisition(osv.osv):
             'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order.requisition'),
         })
         return super(purchase_requisition, self).copy(cr, uid, id, default, context)
-    
+
+    def unlink(self, cr, uid, ids, context=None):
+        """
+        Deletes Requisition and related RFQs 
+        """
+        if context is None: context = {}
+        purchase_obj = self.pool.get('purchase.order')
+        purchase_ids = self._requisition_procurement_cancel(cr, uid, ids, context=context)
+        if purchase_ids:
+            purchase_obj.unlink(cr, uid, purchase_ids, context=context)
+        return super(purchase_requisition, self).unlink(cr, uid, ids, context=context)
+
+    def _requisition_procurement_cancel(self, cr, uid, ids, context=None):
+        """
+        Cancels procurement order related to requisition
+        @param ids: requisition ids
+        @return: Returns purchase orders associated with requisition if any
+        """
+        if context is None: context = {}
+        purchase_ids = []
+        procurement_ids = []
+        procurement_obj = self.pool.get('procurement.order')
+        for requisition in self.browse(cr, uid, ids, context=context):
+            purchase_ids.extend(purchase.id for purchase in requisition.purchase_ids)
+            if requisition.state == 'cancel':
+                continue
+            procurement_ids.extend(procurement_obj.search(cr, uid,
+                [('requisition_id', '=', requisition.id)], context=context))
+        if procurement_ids:
+            procurement_obj.action_cancel(cr, uid, procurement_ids)
+        return purchase_ids
+
     def tender_cancel(self, cr, uid, ids, context=None):
+        if context is None: context = {}
         purchase_order_obj = self.pool.get('purchase.order')
-        for purchase in self.browse(cr, uid, ids, context=context):
-            for purchase_id in purchase.purchase_ids:
-                if str(purchase_id.state) in('draft'):
-                    purchase_order_obj.action_cancel(cr,uid,[purchase_id.id])
+        purchase_ids = self._requisition_procurement_cancel(cr, uid, ids, context=context)
+        if purchase_ids:
+            purchase_order_obj.action_cancel(cr, uid, purchase_ids, context=context)
         procurement_ids = self.pool['procurement.order'].search(cr, uid, [('requisition_id', 'in', ids)], context=context)
         self.pool['procurement.order'].action_done(cr, uid, procurement_ids)
-        return self.write(cr, uid, ids, {'state': 'cancel'})
+        return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
 
     def tender_in_progress(self, cr, uid, ids, context=None):
         return self.write(cr, uid, ids, {'state':'in_progress'} ,context=context)
index 389c328..64c1d1b 100644 (file)
@@ -2,25 +2,25 @@
   I cancel requisition.
 -
   !python {model: purchase.requisition}: |
-    self.tender_cancel(cr, uid, [ref("requisition1")])
+    self.tender_cancel(cr, uid, [ref("requisition2")])
 -
   I check requisition after cancelled.
 -
-  !assert {model: purchase.requisition, id: requisition1}:
+  !assert {model: purchase.requisition, id: requisition2}:
     - state == 'cancel'
 -
   I reset requisition as "New".
 -
   !python {model: purchase.requisition}: |
-    self.tender_reset(cr, uid, [ref('requisition1')])
+    self.tender_reset(cr, uid, [ref('requisition2')])
 -
   I duplicate requisition.
 -
   !python {model: purchase.requisition}: |
-    self.copy(cr, uid, ref('requisition1'))
+    self.copy(cr, uid, ref('requisition2'))
 -
   I delete requisition.
 -
-  !python {model: purchase.order}: |
-    self.unlink(cr, uid, [ref("requisition1")])
+  !python {model: purchase.requisition}: |
+    self.unlink(cr, uid, [ref("requisition2")])
 
index 38e6678..e10e08d 100644 (file)
     (data, format) = netsvc.LocalService('report.purchase.requisition').create(cr, uid, [ref('purchase_requisition.requisition1')], {}, {})
     if tools.config['test_report_directory']:
         file(os.path.join(tools.config['test_report_directory'], 'purchase_requisition-purchase_requisition_report.'+format), 'wb+').write(data)
+-
+  I check that I cannot cancel the requisision
+-
+  !python {model: purchase.requisition}: |
+    from openerp.osv.osv import except_osv
+    try:
+        self.tender_cancel(cr, uid, [ref("requisition1")])
+    except except_osv, exc:
+        assert exc.args == (u'Unable to cancel this purchase order.', u'First cancel all receptions related to this purchase order.')
+    else:
+        assert False, 'tender_cancel should have failed'
index 5e441f2..628997e 100644 (file)
@@ -7,4 +7,10 @@
       - product_id: product.product_product_9
         product_qty: 10.0
         product_uom_id: product.product_uom_unit
-  
+-
+  !record {model: purchase.requisition, id: requisition2}:
+    exclusive: exclusive
+    line_ids:
+      - product_id: product.product_product_13
+        product_qty: 10.0
+        product_uom_id: product.product_uom_unit
index 884893a..cf1c447 100644 (file)
@@ -4,7 +4,8 @@
 # Copyright (c) 2010 Camptocamp SA (http://www.camptocamp.com) 
 # All Right Reserved
 #
-# Author : Nicolas Bessi (Camptocamp)
+# Authors : Nicolas Bessi (Camptocamp)
+#           Yannick Vaucher (Camptocamp)
 #
 # WARNING: This program as such is intended to be used by professional
 # programmers who take the whole responsability of assessing all potential
@@ -30,6 +31,8 @@
 ##############################################################################
 
 from openerp import pooler
+from openerp.osv import orm
+from tools.translate import _
 
 class WebKitHelper(object):
     """Set of usefull report helper"""
@@ -39,19 +42,19 @@ class WebKitHelper(object):
         self.uid = uid
         self.pool = pooler.get_pool(self.cursor.dbname)
         self.report_id = report_id
-        
-    def embed_image(self, type, img, width=0, height=0) :
+
+    def embed_image(self, type, img, width=0, height=0, unit="px"):
         "Transform a DB image into an embedded HTML image"
 
         if width :
-            width = 'width="%spx"'%(width)
+            width = 'width: %s%s;'%(width, unit)
         else :
             width = ' '
         if height :
-            height = 'height="%spx"'%(height)
+            height = 'height: %s%s;'%(height, unit)
         else :
             height = ' '
-        toreturn = '<img %s %s src="data:image/%s;base64,%s" />'%(
+        toreturn = '<img style="%s%s" src="data:image/%s;base64,%s" />'%(
             width,
             height,
             type, 
@@ -59,26 +62,32 @@ class WebKitHelper(object):
         return toreturn
             
             
-    def get_logo_by_name(self, name):
+    def get_logo_by_name(self, name, company_id=None):
         """Return logo by name"""
         header_obj = self.pool.get('ir.header_img')
-        header_img_id = header_obj.search(
-                                            self.cursor, 
-                                            self.uid, 
-                                            [('name','=',name)]
-                                        )
+        domain = [('name','=',name)]
+        if company_id:
+            domain.append(('company_id', '=', company_id))
+        header_img_id = header_obj.search(self.cursor, 
+                                          self.uid, 
+                                          domain)
         if not header_img_id :
-            return u''
+            msg = _("No header image named '%s' found.") % name
+            if company_id:
+                company_obj = self.pool.get('res.company')
+                company = company_obj.browse(self.cursor, self.uid, company_id)
+                msg = _("No header image named '%s' found for company %s.") % (name, company.name)
+            raise orm.except_orm('Error', msg)
         if isinstance(header_img_id, list):
             header_img_id = header_img_id[0]
 
         head = header_obj.browse(self.cursor, self.uid, header_img_id)
         return (head.img, head.type)
             
-    def embed_logo_by_name(self, name, width=0, height=0):
+    def embed_logo_by_name(self, name, width=0, height=0, unit="px", company_id=None):
         """Return HTML embedded logo by name"""
-        img, type = self.get_logo_by_name(name)
-        return self.embed_image(type, img, width, height)
+        img, type = self.get_logo_by_name(name, company_id=company_id)
+        return self.embed_image(type, img, width, height, unit)
         
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index cccaec7..74debf3 100644 (file)
@@ -245,6 +245,7 @@ class WebKitParser(report_sxw):
         body_mako_tpl = mako_template(template)
         helper = WebKitHelper(cursor, uid, report_xml.id, context)
         if report_xml.precise_mode:
+            objs = parser_instance.localcontext['objects']
             for obj in objs:
                 parser_instance.localcontext['objects'] = [obj]
                 try :
index d389715..1aa4ee7 100644 (file)
@@ -340,7 +340,7 @@ class sale_order(osv.osv):
         return {
             'name': pick_name,
             'origin': order.name,
-            'date': self.date_to_datetime(cr, uid, order.date_order, context),
+            'date': self.date_to_datetime(cr, uid, order.date_confirm, context),
             'type': 'out',
             'state': 'auto',
             'move_type': order.picking_policy,
@@ -415,7 +415,7 @@ class sale_order(osv.osv):
             if line.state == 'done':
                 continue
 
-            date_planned = self._get_date_planned(cr, uid, order, line, order.date_order, context=context)
+            date_planned = self._get_date_planned(cr, uid, order, line, order.date_confirm, context=context)
 
             if line.product_id:
                 if line.product_id.type in ('product', 'consu'):
index 0b7249f..0920a90 100644 (file)
@@ -40,7 +40,7 @@
                    <xpath expr="//button[@name='action_view_invoice']" position="after">
                        <field name="picking_ids" invisible="1"/>
                        <button name="action_view_delivery" string="View Delivery Order" type="object" class="oe_highlight"
-                           attrs="{'invisible': ['|','|','|',('picking_ids','=',False),('picking_ids','=',[]), ('state', 'not in', ('progress','manual')),('shipped','=',True)]}" groups="base.group_user"/>
+                           attrs="{'invisible': ['|',('picking_ids','=',False),('picking_ids','=',[])]}" groups="base.group_user"/>
                    </xpath>
                     <xpath expr="//button[@name='action_cancel']" position="after">
                         <button name="ship_cancel" states="shipping_except" string="Cancel Order"/>
index c69ea6d..6efa1f5 100644 (file)
@@ -31,6 +31,9 @@ class stock_move(osv.osv):
         values = super(stock_move, self)._prepare_chained_picking(cr, uid, picking_name, picking, picking_type, moves_todo, context=context)
         if picking.sale_id:
             values['sale_id'] = picking.sale_id.id
+            if values.get('type') == 'out' and picking.sale_id.order_policy == 'picking':
+                values['invoice_state'] = '2binvoiced'
+        picking.write({'invoice_state': 'none'})
         return values
 
 class stock_picking(osv.osv):
index 12f42a4..e12ab01 100644 (file)
@@ -21,7 +21,7 @@
 
 {
     'name': 'Warehouse Management',
-    'version': '1.1',
+    'version': '1.1.1',
     'author': 'OpenERP SA',
     'summary': 'Inventory, Logistic, Storage',
     'description' : """
@@ -95,6 +95,7 @@ Dashboard / Reports for Warehouse Management will include:
         'test/opening_stock.yml',
         'test/shipment.yml',
         'test/stock_report.yml',
+        'test/stock_move_chain_validation.yml',
         'test/setlast_tracking.yml',
     ],
     'installable': True,
diff --git a/addons/stock/migrations/7.0.1.1.1/pre-rename_sequence_code.py b/addons/stock/migrations/7.0.1.1.1/pre-rename_sequence_code.py
new file mode 100644 (file)
index 0000000..1831865
--- /dev/null
@@ -0,0 +1,11 @@
+__name__ = ("update internal picking sequence code and sequence")
+
+def migrate(cr, version):
+    old_type = 'stock.picking'
+    new_type = 'stock.picking.internal'
+    cr.execute ("UPDATE ir_sequence_type SET code=%(newtype)s WHERE code=%(oldtype)s",
+                {'newtype': new_type,
+                 'oldtype': old_type})
+    cr.execute ("UPDATE ir_sequence SET code=%(newtype)s WHERE code=%(oldtype)s",
+                {'newtype': new_type,
+                 'oldtype': old_type})
index 77adebb..869f391 100644 (file)
@@ -74,7 +74,6 @@
                         <div attrs="{'invisible':[('type','=','service')]}">
                             <field name="produce_delay" class="oe_inline"/> days
                         </div>
-                        <field name="active"/>
                     </group>
                 </group>
                 <xpath expr="//group[@string='Sale Conditions']" position="inside">
@@ -83,6 +82,9 @@
                             <field name="sale_delay" class="oe_inline"/> days
                         </div>
                 </xpath>
+                <group name="status" position="inside" version="7.0">
+                    <field name="active"/>
+                </group>
                 <group name="status" position="before" version="7.0">
                     <group string="Stock and Expected Variations" attrs="{'invisible': [('type', '=', 'service')]}" groups="base.group_user">
                         <label for="qty_available"/>
index 0aac0d6..258a2e4 100644 (file)
@@ -137,13 +137,15 @@ stock_report_tracklots()
 
 class report_stock_lines_date(osv.osv):
     _name = "report.stock.lines.date"
-    _description = "Dates of Inventories"
+    _description = "Dates of Inventories and latest Moves"
     _auto = False
     _order = "date"
     _columns = {
-        'id': fields.integer('Inventory Line Id', readonly=True),
+        'id': fields.integer('Product Id', readonly=True),
         'product_id': fields.many2one('product.product', 'Product', readonly=True, select=True),
-        'date': fields.datetime('Latest Inventory Date'),
+        'date': fields.datetime('Date of latest Inventory', readonly=True),
+        'move_date': fields.datetime('Date of latest Stock Move', readonly=True),
+        "active" : fields.boolean("Active", readonly=True),
     }
     def init(self, cr):
         drop_view_if_exists(cr, 'report_stock_lines_date')
@@ -152,13 +154,16 @@ class report_stock_lines_date(osv.osv):
                 select
                 p.id as id,
                 p.id as product_id,
-                max(s.date) as date
+                max(s.date) as date,
+                max(m.date) as move_date,
+                               p.active as active
             from
                 product_product p
-                    left outer join stock_inventory_line l on (p.id=l.product_id)
-                    left join stock_inventory s on (l.inventory_id=s.id)
-                and s.state = 'done'
-                where p.active='true'
+                    left join (
+                        stock_inventory_line l
+                        inner join stock_inventory s on (l.inventory_id=s.id and s.state = 'done')
+                    ) on (p.id=l.product_id)
+                    left join stock_move m on (m.product_id=p.id and m.state = 'done')
                 group by p.id
             )""")
 
index f56d738..267b114 100644 (file)
@@ -23,6 +23,7 @@
                 <tree string="Dates of Inventories" create="false">
                     <field name="product_id"/>
                     <field name="date" />
+                    <field name="move_date"/>
                 </tree>
             </field>
         </record>
             <field name="name">report.stock.lines.date.search</field>
             <field name="model">report.stock.lines.date</field>
             <field name="arch" type="xml">
-                <search string="Dates of Inventories">
+                <search string="Dates of Inventories &amp; Moves">
                     <field name="date"/>
                     <filter icon="terp-accessories-archiver" name="stockable" string="Stockable" domain="[('product_id.type','=', 'product')]"/>
                     <filter icon="terp-accessories-archiver" string="Consumable" domain="[('product_id.type','=', 'consu')]"/>
                     <separator/>
-                    <filter icon="terp-accessories-archiver-minus" string="Non Inv" domain="[('date','=', False)]"/>
+                    <filter icon="terp-accessories-archiver-minus" string="No Inventory yet" domain="[('date','=', False)]"/>
+                    <filter icon="terp-accessories-archiver-minus" string="No Stock Move yet" domain="[('move_date','=', False)]"/>
                     <field name="product_id"/>
                 </search>
             </field>
             <field name="name">report.stock.lines.date.form</field>
             <field name="model">report.stock.lines.date</field>
             <field name="arch" type="xml">
-                <form string="Dates of Inventories" version="7.0">
+                <form string="Dates of Inventories &amp; Moves" version="7.0">
                     <group>
                         <field name="product_id"/>
                         <field name="date"/>
+                        <field name="move_date"/>
                     </group>
                 </form>
             </field>
         </record>
 
         <record model="ir.actions.act_window" id="action_stock_line_date">
-            <field name="name">Last Product Inventories</field>
+            <field name="name">Latest Inventories &amp; Moves</field>
             <field name="res_model">report.stock.lines.date</field>
             <field name="view_type">form</field>
              <field name="context">{'search_default_stockable':1}</field>
             <field name="view_mode">tree,form</field>
-            <field name="help">Display the last inventories done on your products and easily sort them with specific filtering criteria. If you do frequent and partial inventories, you need this report in order to ensure that the stock of each product is controlled at least once a year.</field>
+            <field name="help">Display the latest Inventories and Moves done on your products and easily sort them with specific filtering criteria. If you do frequent and partial inventories, you need this report in order to ensure that the stock of each product is controlled at least once a year. This also lets you find out which products have seen little move lately and may deserve special measures (discounted sale, quality control...)</field>
         </record>
 
         <menuitem parent="next_id_61" action="action_stock_line_date" id="menu_report_stock_line_date" sequence="2"/>
index bd15c45..84fb55d 100644 (file)
@@ -629,10 +629,20 @@ class stock_picking(osv.osv):
             res[pick]['min_date'] = dt1
             res[pick]['max_date'] = dt2
         return res
+    
+    def _get_stock_move_changes(self, cr, uid, ids, context=None):
+        '''Return the ids of pickings that should change, due to changes
+        in stock moves.'''
+        move_pool = self.pool['stock.move']
+        picking_ids = set()
+        for move_obj in move_pool.browse(cr, uid, ids, context=context):
+            if move_obj.picking_id:
+                picking_ids.add(move_obj.picking_id.id)
+        return list(picking_ids)
 
     def create(self, cr, user, vals, context=None):
         if ('name' not in vals) or (vals.get('name')=='/'):
-            seq_obj_name =  self._name
+            seq_obj_name =  'stock.picking.%s' % vals.get('type', 'internal')
             vals['name'] = self.pool.get('ir.sequence').get(cr, user, seq_obj_name)
         new_id = super(stock_picking, self).create(cr, user, vals, context)
         return new_id
@@ -664,12 +674,31 @@ class stock_picking(osv.osv):
             * Transferred: has been processed, can't be modified or cancelled anymore\n
             * Cancelled: has been cancelled, can't be confirmed anymore"""
         ),
-        'min_date': fields.function(get_min_max_date, fnct_inv=_set_minimum_date, multi="min_max_date",
-                 store=True, type='datetime', string='Scheduled Time', select=1, help="Scheduled time for the shipment to be processed"),
+        'min_date': fields.function(
+            get_min_max_date,
+            fnct_inv=_set_minimum_date, multi='min_max_date',
+            store={
+                'stock.move': (
+                    _get_stock_move_changes,
+                    ['date_expected'], 10,
+                )
+            },
+            type='datetime', string='Scheduled Time', select=True,
+            help="Scheduled time for the shipment to be processed"
+        ),
         'date': fields.datetime('Creation Date', help="Creation date, usually the time of the order.", select=True, states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
         'date_done': fields.datetime('Date of Transfer', help="Date of Completion", states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
-        'max_date': fields.function(get_min_max_date, fnct_inv=_set_maximum_date, multi="min_max_date",
-                 store=True, type='datetime', string='Max. Expected Date', select=2),
+        'max_date': fields.function(
+            get_min_max_date,
+            fnct_inv=_set_maximum_date, multi='min_max_date',
+            store={
+                'stock.move': (
+                    _get_stock_move_changes,
+                    ['date_expected'], 10,
+                )
+            },
+            type='datetime', string='Max. Expected Date', select=True
+        ),
         'move_lines': fields.one2many('stock.move', 'picking_id', 'Internal Moves', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
         'product_id': fields.related('move_lines', 'product_id', type='many2one', relation='product.product', string='Product'),
         'auto_picking': fields.boolean('Auto-Picking', states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
@@ -749,7 +778,12 @@ class stock_picking(osv.osv):
         @return: True
         """
         pickings = self.browse(cr, uid, ids, context=context)
-        self.write(cr, uid, ids, {'state': 'confirmed'})
+        to_update = []
+        for pick in pickings:
+            if pick.state != 'confirmed':
+                to_update.append(pick.id)
+        if to_update:
+            self.write(cr, uid, to_update, {'state': 'confirmed'})
         todo = []
         for picking in pickings:
             for r in picking.move_lines:
@@ -817,18 +851,21 @@ class stock_picking(osv.osv):
         """ Cancels picking and moves.
         @return: True
         """
-        wf_service = netsvc.LocalService("workflow")
         for pick in self.browse(cr, uid, ids):
             move_ids = [x.id for x in pick.move_lines]
             self.pool.get('stock.move').cancel_assign(cr, uid, move_ids)
-            wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
         return True
 
     def action_assign_wkf(self, cr, uid, ids, context=None):
         """ Changes picking state to assigned.
         @return: True
         """
-        self.write(cr, uid, ids, {'state': 'assigned'})
+        to_update = []
+        for pick in self.browse(cr, uid, ids, context=context):
+            if pick.state != 'assigned':
+                to_update.append(pick.id)
+        if to_update:
+            self.write(cr, uid, to_update, {'state': 'assigned'})
         return True
 
     def test_finished(self, cr, uid, ids):
@@ -858,6 +895,8 @@ class stock_picking(osv.osv):
                 if all([x.state != 'waiting' for x in pick.move_lines]):
                     return True
             for move in pick.move_lines:
+                if (move.state) == 'waiting':
+                    move.check_assign()
                 if (move.state in ('confirmed', 'draft')) and (mt == 'one'):
                     return False
                 if (mt == 'direct') and (move.state == 'assigned') and (move.product_qty):
@@ -1322,7 +1361,7 @@ class stock_picking(osv.osv):
                             'product_uos_qty': uos_qty[move.id],
                             'picking_id' : new_picking,
                             'state': 'assigned',
-                            'move_dest_id': False,
+                            'move_dest_id': move.move_dest_id.id,
                             'price_unit': move.price_unit,
                             'product_uom': product_uoms[move.id]
                     }
@@ -2160,10 +2199,12 @@ class stock_move(osv.osv):
         # fix for bug lp:707031
         # called write of related picking because changing move availability does
         # not trigger workflow of picking in order to change the state of picking
+        seen = set()
         wf_service = netsvc.LocalService('workflow')
         for move in self.browse(cr, uid, ids, context):
-            if move.picking_id:
+            if move.picking_id and move.picking_id.id not in seen:
                 wf_service.trg_write(uid, 'stock.picking', move.picking_id.id, cr)
+                seen.add(move.picking_id.id)
         return True
 
     #
@@ -2197,7 +2238,11 @@ class stock_move(osv.osv):
                     pickings[move.picking_id.id] = 1
                     r = res.pop(0)
                     product_uos_qty = self.pool.get('stock.move').onchange_quantity(cr, uid, [move.id], move.product_id.id, r[0], move.product_id.uom_id.id, move.product_id.uos_id.id)['value']['product_uos_qty']
-                    cr.execute('update stock_move set location_id=%s, product_qty=%s, product_uos_qty=%s where id=%s', (r[1], r[0],product_uos_qty, move.id))
+                    move.write({
+                        'location_id': r[1],
+                        'product_qty': r[0],
+                        'product_uos_qty': product_uos_qty,
+                        })
 
                     while res:
                         r = res.pop(0)
@@ -2408,7 +2453,6 @@ class stock_move(osv.osv):
                 todo.append(move.id)
         if todo:
             self.action_confirm(cr, uid, todo, context=context)
-            todo = []
 
         for move in self.browse(cr, uid, ids, context=context):
             if move.state in ['done','cancel']:
@@ -2432,15 +2476,11 @@ class stock_move(osv.osv):
 
             self._create_product_valuation_moves(cr, uid, move, context=context)
             if move.state not in ('confirmed','done','assigned'):
-                todo.append(move.id)
-
-        if todo:
-            self.action_confirm(cr, uid, todo, context=context)
-
-        self.write(cr, uid, move_ids, {'state': 'done', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
-        for id in move_ids:
-             wf_service.trg_trigger(uid, 'stock.move', id, cr)
-
+                self.action_confirm(cr, uid, [move.id], context=context)
+            self.write(cr, uid, [move.id], 
+                       {'state': 'done', 
+                       'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, 
+                       context=context)
         for pick_id in picking_ids:
             wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
 
@@ -2456,6 +2496,7 @@ class stock_move(osv.osv):
         debit_line_vals = {
                     'name': move.name,
                     'product_id': move.product_id and move.product_id.id or False,
+                    'product_uom_id': move.product_uom and move.product_uom.id or False,
                     'quantity': move.product_qty,
                     'ref': move.picking_id and move.picking_id.name or False,
                     'date': time.strftime('%Y-%m-%d'),
@@ -2466,6 +2507,7 @@ class stock_move(osv.osv):
         credit_line_vals = {
                     'name': move.name,
                     'product_id': move.product_id and move.product_id.id or False,
+                    'product_uom_id': move.product_uom and move.product_uom.id or False,
                     'quantity': move.product_qty,
                     'ref': move.picking_id and move.picking_id.name or False,
                     'date': time.strftime('%Y-%m-%d'),
@@ -2757,7 +2799,7 @@ class stock_move(osv.osv):
                             'product_uos_qty': product_qty,
                             'picking_id' : move.picking_id.id,
                             'state': 'assigned',
-                            'move_dest_id': False,
+                            'move_dest_id': move.move_dest_id.id,
                             'price_unit': move.price_unit,
                             }
                 prodlot_id = prodlot_ids[move.id]
@@ -2929,6 +2971,17 @@ class stock_inventory_line(osv.osv):
     _name = "stock.inventory.line"
     _description = "Inventory Line"
     _rec_name = "inventory_id"
+    _order = "inventory_id, location_name, product_code, product_name, prodlot_name"
+
+    def _get_product_name_change(self, cr, uid, ids, context=None):
+        return self.pool.get('stock.inventory.line').search(cr, uid, [('product_id', 'in', ids)], context=context)
+
+    def _get_location_change(self, cr, uid, ids, context=None):
+        return self.pool.get('stock.inventory.line').search(cr, uid, [('location_id', 'in', ids)], context=context)
+        
+    def _get_prodlot_change(self, cr, uid, ids, context=None):
+        return self.pool.get('stock.inventory.line').search(cr, uid, [('prod_lot_id', 'in', ids)], context=context)
+
     _columns = {
         'inventory_id': fields.many2one('stock.inventory', 'Inventory', ondelete='cascade', select=True),
         'location_id': fields.many2one('stock.location', 'Location', required=True),
@@ -2938,6 +2991,18 @@ class stock_inventory_line(osv.osv):
         'company_id': fields.related('inventory_id','company_id',type='many2one',relation='res.company',string='Company',store=True, select=True, readonly=True),
         'prod_lot_id': fields.many2one('stock.production.lot', 'Serial Number', domain="[('product_id','=',product_id)]"),
         'state': fields.related('inventory_id','state',type='char',string='Status',readonly=True),
+        'product_name': fields.related('product_id', 'name', type='char', string='Product name', store={
+                                                                                            'product.product': (_get_product_name_change, ['name', 'default_code'], 20),
+                                                                                            'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['product_id'], 20),}),
+        'product_code': fields.related('product_id', 'default_code', type='char', string='Product code', store={
+                                                                                            'product.product': (_get_product_name_change, ['name', 'default_code'], 20),
+                                                                                            'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['product_id'], 20),}),
+        'location_name': fields.related('location_id', 'complete_name', type='char', string='Location name', store={
+                                                                                            'stock.location': (_get_location_change, ['name', 'location_id', 'active'], 20),
+                                                                                            'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['location_id'], 20),}),
+        'prodlot_name': fields.related('prod_lot_id', 'name', type='char', string='Serial Number name', store={
+                                                                                            'stock.production.lot': (_get_prodlot_change, ['name'], 20),
+                                                                                            'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['prod_lot_id'], 20),}),
     }
 
     def _default_stock_location(self, cr, uid, context=None):
index c3569c4..6c0b3e8 100644 (file)
@@ -17,7 +17,7 @@
         
         <record id="seq_type_picking_internal" model="ir.sequence.type">
             <field name="name">Picking INT</field>
-            <field name="code">stock.picking</field>
+            <field name="code">stock.picking.internal</field>
         </record>
 
         <!--
@@ -42,7 +42,7 @@
         
         <record id="seq_picking_internal" model="ir.sequence">
             <field name="name">Picking INT</field>
-            <field name="code">stock.picking</field>
+            <field name="code">stock.picking.internal</field>
             <field name="prefix">INT/</field>
             <field name="padding">5</field>
             <field name="company_id" eval="False"/>
index 6a58107..28e35d1 100644 (file)
             <field name="model">stock.production.lot</field>
             <field name="arch" type="xml">
                 <form string="Serial Number" version="7.0">
-                    <div class="oe_button_box oe_right">
-                        <button name="action_traceability" string="Upstream Traceability" type="object" context="{'type': 'move_history_ids2', 'field': 'prodlot_id'}"/>
-                        <button name="action_traceability" string="Downstream Traceability" type="object" context="{'type': 'move_history_ids', 'field': 'prodlot_id'}"/>
-                    </div>
-                    <div class="oe_title">
-                        <label for="name" class="oe_edit_only"/>
-                        <h1>
-                            <field name="name"/>
-                        </h1>
-                    </div>
+                    <group>
+                        <div>
+                            <div class="oe_button_box oe_right">
+                                <button name="action_traceability" string="Upstream Traceability" type="object" context="{'type': 'move_history_ids2', 'field': 'prodlot_id'}"/>
+                                <button name="action_traceability" string="Downstream Traceability" type="object" context="{'type': 'move_history_ids', 'field': 'prodlot_id'}"/>
+                            </div>
+                            <div class="oe_title">
+                                <label for="name" class="oe_edit_only"/>
+                                <h1>
+                                    <field name="name"/>
+                                </h1>
+                            </div>
+                        </div>
+                    </group>
                     <group>
                         <group>
                             <field name="product_id"/>
                         <group>
                             <field name="partner_id" on_change="onchange_partner_in(partner_id)"/>
                             <field name="backorder_id" readonly="1" attrs="{'invisible': [('backorder_id','=',False)]}"/>
-                            <field name="invoice_state" string="Invoice Control" groups="account.group_account_invoice" attrs="{'invisible':[('invoice_state', '=', 'none')]}"/>
+                            <field name="invoice_state" string="Invoice Control" groups="account.group_account_invoice"/>
                             <field name="stock_journal_id" widget="selection" groups="account.group_account_user"/>
                         </group>
                         <group>
diff --git a/addons/stock/test/multicompany.yml b/addons/stock/test/multicompany.yml
new file mode 100644 (file)
index 0000000..2c0353a
--- /dev/null
@@ -0,0 +1,68 @@
+
+-
+  Set the current user as multicompany user
+-
+  !context
+    uid: 'stock.multicompany_user'
+
+-
+  check no error on getting default stock.move values in multicompany setting
+-
+  !python {model: stock.move}: |
+    location_obj = self.pool.get('stock.location')
+    fields = ['location_id', 'location_dest_id']
+    for type in ('in', 'internal', 'out'):
+        context['picking_type'] = type
+        defaults = self.default_get(cr, uid, ['location_id', 'location_dest_id', 'type'], context)
+        log('type: %s got defaults: %s', type, defaults)
+        for field in fields:
+            if defaults.get(field):
+                try:
+                    location_obj.check_access_rule(cr, uid, [defaults[field]], 'read', context)
+                except Exception, exc:
+                    assert False, "unreadable location %s: %s" % (field, exc)
+        assert defaults['type'] == type, "wrong move type"
+
+-
+  check onchange_move_type does not return unreadable in multicompany setting
+-
+  !python {model: stock.move}: |
+    location_obj = self.pool.get('stock.location')
+    fields = ['location_id', 'location_dest_id']
+    for type in ('in', 'internal', 'out'):
+        result = self.onchange_move_type(cr, uid, [], type, context)['value']
+        log('type: %s got: %s', type, result)
+        for field in fields:
+            if result.get(field):
+                try:
+                    location_obj.check_access_rule(cr, uid, [result[field]], 'read', context)
+                except Exception, exc:
+                    assert False, "unreadable location %s: %s" % (field, exc)
+
+-
+  check default location readability for stock_fill_inventory in multicompany setting
+-
+  !python {model: stock.fill.inventory}: |
+    location_obj = self.pool.get('stock.location')
+    defaults = self.default_get(cr, uid, ['location_id'], context)
+    log('got defaults: %s', defaults)
+    if defaults.get('location_id'):
+        try:
+            location_obj.check_access_rule(cr, uid, [defaults['location_id']], 'read', context)
+        except Exception, exc:
+            assert False, "unreadable source location: %s" % exc
+
+-
+  check default locations for warehouse in multicompany setting
+-
+  !python {model: stock.warehouse}: |
+    location_obj = self.pool.get('stock.location')
+    fields = ['lot_input_id', 'lot_stock_id', 'lot_output_id']
+    defaults = self.default_get(cr, uid, fields, context)
+    log('got defaults: %s', defaults)
+    for field in fields:
+        if defaults.get(field):
+            try:
+                location_obj.check_access_rule(cr, uid, [defaults[field]], 'read', context)
+            except Exception, exc:
+                assert False, "unreadable default %s: %s" % (field, exc)
diff --git a/addons/stock/test/stock_move_chain_validation.yml b/addons/stock/test/stock_move_chain_validation.yml
new file mode 100644 (file)
index 0000000..3fd0c7b
--- /dev/null
@@ -0,0 +1,97 @@
+-
+ I create an outgoing move for 2 LCD17
+-
+  !record {model: stock.move, id: dest_move_lcd17}:
+     product_qty: 2
+     product_id: product.product_product_7
+     product_uom: product.product_uom_unit
+     location_id: stock_location_components
+     location_dest_id: stock_location_customers
+-
+ I create 2 moves for 1 LCD17 chained with the outgoing one
+-
+  !record {model: stock.move, id: move_lcd17_1}:
+     product_qty: 1
+     product_id: product.product_product_7
+     product_uom: product.product_uom_unit
+     location_id: stock_location_stock
+     location_dest_id: stock_location_components
+     move_dest_id: dest_move_lcd17
+-
+  !record {model: stock.move, id: move_lcd17_2}:
+     product_qty: 1
+     product_id: product.product_product_7
+     product_uom: product.product_uom_unit
+     location_id: stock_location_stock
+     location_dest_id: stock_location_components
+     move_dest_id: dest_move_lcd17
+-
+ I confirm all moves
+-
+ !python {model: stock.move}: |
+   self.action_confirm(cr, uid, [ref('stock.dest_move_lcd17'),ref('stock.move_lcd17_1'),ref('stock.move_lcd17_2')], context=context)
+-
+  I process the 2 moves chained with the outgoing move
+-
+ !python {model: stock.move}: |
+   self.action_done(cr, uid, [ref('stock.move_lcd17_1'),ref('stock.move_lcd17_2')], context=context)
+-
+  the outgoing move must be assigned
+-
+ !python {model: stock.move}: |
+   move = self.browse(cr, uid, ref('stock.dest_move_lcd17'), context=context)
+   assert move.state == 'assigned', "out move was not assigned when internal moves where processed"
+-
+ I create an outgoing move for 2 LCD17
+-
+  !record {model: stock.move, id: dest_move2_lcd17}:
+     product_qty: 2
+     product_id: product.product_product_7
+     product_uom: product.product_uom_unit
+     location_id: stock_location_components
+     location_dest_id: stock_location_customers
+-
+ I create 2 moves for 1 LCD17 chained with the outgoing one
+-
+  !record {model: stock.move, id: move2_lcd17_1}:
+     product_qty: 1
+     product_id: product.product_product_7
+     product_uom: product.product_uom_unit
+     location_id: stock_location_stock
+     location_dest_id: stock_location_components
+     move_dest_id: dest_move2_lcd17
+-
+  !record {model: stock.move, id: move2_lcd17_2}:
+     product_qty: 1
+     product_id: product.product_product_7
+     product_uom: product.product_uom_unit
+     location_id: stock_location_stock
+     location_dest_id: stock_location_components
+     move_dest_id: dest_move2_lcd17
+-
+ I confirm all moves
+-
+ !python {model: stock.move}: |
+   self.action_confirm(cr, uid, [ref('stock.dest_move2_lcd17'),ref('stock.move2_lcd17_1'),ref('stock.move2_lcd17_2')], context=context)
+-
+  I process the 1st move chained with the outgoing move
+-
+ !python {model: stock.move}: |
+   self.action_done(cr, uid, [ref('stock.move2_lcd17_1')], context=context)
+-
+  the outgoing move must not be assigned
+-
+ !python {model: stock.move}: |
+   move = self.browse(cr, uid, ref('stock.dest_move2_lcd17'), context=context)
+   assert move.state != 'assigned', "out move was assigned when only 1st internal moves where processed"
+-
+  I process the 2nd move chained with the outgoing move
+-
+ !python {model: stock.move}: |
+   self.action_done(cr, uid, [ref('stock.move2_lcd17_2')], context=context)
+-
+  the outgoing move must not be assigned
+-
+ !python {model: stock.move}: |
+   move = self.browse(cr, uid, ref('stock.dest_move2_lcd17'), context=context)
+   assert move.state == 'assigned', "out move was not assigned when internal moves where processed"
index 0dc6b37..6de786f 100644 (file)
@@ -27,6 +27,9 @@ class stock_fill_inventory(osv.osv_memory):
     _name = "stock.fill.inventory"
     _description = "Import Inventory"
 
+    # Maximum size of a batch of lines we can import without risking OOM
+    MAX_IMPORT_LINES = 10000
+
     def _default_location(self, cr, uid, ids, context=None):
         try:
             location = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'stock_location_stock')
@@ -103,23 +106,31 @@ class stock_fill_inventory(osv.osv_memory):
         for location in location_ids:
             datas = {}
             res[location] = {}
-            move_ids = move_obj.search(cr, uid, ['|',('location_dest_id','=',location),('location_id','=',location),('state','=','done')], context=context)
+            all_move_ids = move_obj.search(cr, uid, ['|',('location_dest_id','=',location),('location_id','=',location),('state','=','done')], context=context)
             local_context = dict(context)
             local_context['raise-exception'] = False
-            for move in move_obj.browse(cr, uid, move_ids, context=context):
-                lot_id = move.prodlot_id.id
-                prod_id = move.product_id.id
-                if move.location_dest_id.id != move.location_id.id:
-                    if move.location_dest_id.id == location:
-                        qty = uom_obj._compute_qty_obj(cr, uid, move.product_uom,move.product_qty, move.product_id.uom_id, context=local_context)
-                    else:
-                        qty = -uom_obj._compute_qty_obj(cr, uid, move.product_uom,move.product_qty, move.product_id.uom_id, context=local_context)
-
-
-                    if datas.get((prod_id, lot_id)):
-                        qty += datas[(prod_id, lot_id)]['product_qty']
-
-                    datas[(prod_id, lot_id)] = {'product_id': prod_id, 'location_id': location, 'product_qty': qty, 'product_uom': move.product_id.uom_id.id, 'prod_lot_id': lot_id}
+            # To avoid running out of memory, process limited batches
+            for i in xrange(0, len(all_move_ids), self.MAX_IMPORT_LINES):
+                move_ids = all_move_ids[i:i+self.MAX_IMPORT_LINES]
+                for move in move_obj.browse(cr, uid, move_ids, context=context):
+                    lot_id = move.prodlot_id.id
+                    prod_id = move.product_id.id
+                    if move.location_dest_id.id != move.location_id.id:
+                        if move.location_dest_id.id == location:
+                            qty = uom_obj._compute_qty_obj(cr, uid, move.product_uom,move.product_qty, move.product_id.uom_id, context=local_context)
+                        else:
+                            qty = -uom_obj._compute_qty_obj(cr, uid, move.product_uom,move.product_qty, move.product_id.uom_id, context=local_context)
+
+
+                        if datas.get((prod_id, lot_id)):
+                            qty += datas[(prod_id, lot_id)]['product_qty']
+
+                        # Floating point sum could introduce tiny rounding errors :
+                        #     Use the UoM API for the rounding (same UoM in & out).
+                        qty = uom_obj._compute_qty_obj(cr, uid,
+                                                       move.product_id.uom_id, qty,
+                                                       move.product_id.uom_id)
+                        datas[(prod_id, lot_id)] = {'product_id': prod_id, 'location_id': location, 'product_qty': qty, 'product_uom': move.product_id.uom_id.id, 'prod_lot_id': lot_id}
 
             if datas:
                 flag = True
index 975c5aa..111a054 100644 (file)
             view_type="form"
             target="new"
             id="action_stock_invoice_onshipping"/>
+
+        <act_window name="Create Invoices"
+            res_model="stock.invoice.onshipping"
+            src_model="stock.picking"
+            key2="client_action_multi"
+            multi="True"
+            view_mode="form"
+            view_type="form"
+            target="new"
+            id="action_stock_invoice_onshipping"/>
     </data>
 </openerp>
index 8e30fc0..91f8104 100644 (file)
@@ -176,6 +176,7 @@ class stock_return_picking(osv.osv_memory):
                                         'name': _('%s-%s-return') % (new_pick_name, pick.name),
                                         'move_lines': [], 
                                         'state':'draft', 
+                                        'backorder_id': False,
                                         'type': new_type,
                                         'date':date_cur, 
                                         'invoice_state': data['invoice_state'],
index 1258ddb..1843907 100644 (file)
@@ -112,7 +112,11 @@ class stock_move(osv.osv):
 
     def _prepare_chained_picking(self, cr, uid, picking_name, picking, picking_type, moves_todo, context=None):
         res = super(stock_move, self)._prepare_chained_picking(cr, uid, picking_name, picking, picking_type, moves_todo, context=context)
-        res.update({'invoice_state': moves_todo[0][1][6] or 'none'})
+        state = moves_todo[0][1][6] or 'none'
+        if picking.sale_id:
+            if res.get('type') == 'out' and picking.sale_id.order_policy == 'picking':
+                state = '2binvoiced'
+        res.update(invoice_state=state)
         return res
 stock_move()
 
index d5e4c3a..b09d10c 100644 (file)
@@ -36,12 +36,12 @@ user name and password for the invitation of the survey.
     'author': 'OpenERP SA',
     'depends': ['mail'],
     'data': [
+        'security/survey_security.xml',
+        'security/ir.model.access.csv',
         'survey_report.xml',
         'survey_data.xml',
         'wizard/survey_selection.xml',
         'wizard/survey_answer.xml',
-        'security/survey_security.xml',
-        'security/ir.model.access.csv',
         'survey_view.xml',
         'wizard/survey_print_statistics.xml',
         'wizard/survey_print_answer.xml',
index b613e22..bfff836 100644 (file)
@@ -22,3 +22,13 @@ access_survey_history_survey_user,survey.history.survey.user,model_survey_histor
 access_survey_response_line_survey_user,survey.response.line.survey.user,model_survey_response_line,base.group_survey_user,1,1,1,1\r
 access_survey_question_column_heading_survey_user,survey.question.column.heading.survey.user,model_survey_question_column_heading,base.group_survey_user,1,0,0,0\r
 access_survey_question_column_heading_user,survey.question.column.heading user,model_survey_question_column_heading,base.group_tool_user,1,1,1,1\r
+access_survey_invitee,survey.invitee,model_survey,base.group_survey_invitee,1,0,0,0\r
+access_survey_page_invitee,survey.page.invitee,model_survey_page,base.group_survey_invitee,1,0,0,0\r
+access_survey_question_invitee,survey.question.invitee,model_survey_question,base.group_survey_invitee,1,0,0,0\r
+access_survey_answer_invitee,survey.answer.invitee,model_survey_answer,base.group_survey_invitee,1,0,0,0\r
+access_survey_response_invitee,survey.response.invitee,model_survey_response,base.group_survey_invitee,1,1,1,0\r
+access_survey_response_line_invitee,survey.response.line.invitee,model_survey_response_line,base.group_survey_invitee,1,1,1,0\r
+access_survey_response_answer_invitee,survey.response.answer.invitee,model_survey_response_answer,base.group_survey_invitee,1,1,1,0\r
+access_survey_history_invitee,survey.history.invitee,model_survey_history,base.group_survey_invitee,1,0,1,0\r
+access_survey_question_column_heading_invitee,survey.question.column.heading.invitee,model_survey_question_column_heading,base.group_survey_invitee,1,0,0,0\r
+access_res_partner_invitee,res.partner.invitee,base.model_res_partner,base.group_survey_invitee,1,0,0,0\r
index 3b0cf58..e4a24e9 100644 (file)
             <field name="name">Survey / User</field>
             <field name="users" eval="[(4, ref('base.user_root'))]"/>
         </record>
-    </data>
+        <record model="res.groups" id="base.group_survey_invitee">
+            <field name="name">Survey / Invitee</field>
+        </record>
+       <record id="rule_survey_invitee" model="ir.rule">
+           <field name="model_id" ref="model_survey" />
+           <field name="domain_force">[("invited_user_ids", "=", user.id)]</field>
+           <field name="groups" eval="[(6, 0,[ref('base.group_survey_invitee')])]" />
+       </record>
+       <record id="rule_survey_page_invitee" model="ir.rule">
+           <field name="model_id" ref="model_survey_page" />
+           <field name="domain_force">[("survey_id.invited_user_ids", "=", user.id)]</field>
+           <field name="groups" eval="[(6, 0,[ref('base.group_survey_invitee')])]" />
+       </record>
+       <record id="rule_survey_question_invitee" model="ir.rule">
+           <field name="model_id" ref="model_survey_question" />
+           <field name="domain_force">[("survey.invited_user_ids", "=", user.id)]</field>
+           <field name="groups" eval="[(6, 0,[ref('base.group_survey_invitee')])]" />
+       </record>
+       <record id="rule_survey_answer_invitee" model="ir.rule">
+           <field name="model_id" ref="model_survey_answer" />
+           <field name="domain_force">[("question_id.survey.invited_user_ids", "=", user.id)]</field>
+           <field name="groups" eval="[(6, 0,[ref('base.group_survey_invitee')])]" />
+       </record>
+       <record id="rule_survey_response_invitee" model="ir.rule">
+           <field name="model_id" ref="model_survey_response" />
+           <field name="domain_force">[("user_id", "=", user.id)]</field>
+           <field name="groups" eval="[(6, 0,[ref('base.group_survey_invitee')])]" />
+       </record>
+       <record id="rule_survey_response_line_invitee" model="ir.rule">
+           <field name="model_id" ref="model_survey_response_line" />
+           <field name="domain_force">[("response_id.user_id", "=", user.id)]</field>
+           <field name="groups" eval="[(6, 0,[ref('base.group_survey_invitee')])]" />
+       </record>
+       <record id="rule_survey_response_answer_invitee" model="ir.rule">
+           <field name="model_id" ref="model_survey_response_answer" />
+           <field name="domain_force">[("response_id.response_id.user_id", "=", user.id)]</field>
+           <field name="groups" eval="[(6, 0,[ref('base.group_survey_invitee')])]" />
+       </record>
+       <record id="rule_survey_history_invitee" model="ir.rule">
+           <field name="model_id" ref="model_survey_history" />
+           <field name="domain_force">[("survey_id.invited_user_ids", "=", user.id)]</field>
+           <field name="groups" eval="[(6, 0,[ref('base.group_survey_invitee')])]" />
+       </record>
+       <record id="rule_survey_question_column_heading_invitee" model="ir.rule">
+           <field name="model_id" ref="model_survey_question_column_heading" />
+           <field name="domain_force">[("question_id.survey.invited_user_ids", "=", user.id)]</field>
+           <field name="groups" eval="[(6, 0,[ref('base.group_survey_invitee')])]" />
+       </record>
+       <record id="rule_res_partner_invitee" model="ir.rule">
+           <field name="model_id" ref="base.model_res_partner" />
+           <field name="domain_force">[("id", "=", user.id)]</field>
+           <field name="groups" eval="[(6, 0,[ref('base.group_survey_invitee')])]" />
+       </record>
+     </data>
 </openerp>
index 647be4c..b8129f9 100644 (file)
@@ -6,20 +6,23 @@
                 id="report_survey_form"
                 model="survey"
                 name="survey.form"
-                string="Survey"/>
+                string="Survey"
+                groups="base.group_survey_user" />
 
         <report auto="True"
                 id="survey_analysis"
                 model="survey"
                 name="survey.analysis"
                 rml=""
-                string="Survey Statistics"/>
+                string="Survey Statistics"
+                groups="base.group_survey_user" />
 
         <report auto="True"
                 id="survey_browse_response"
                 model="survey"
                 name="survey.browse.response"
                 rml=""
-                string="Survey Answers"/>
+                string="Survey Answers"
+                groups="base.group_survey_user" />
     </data>
 </openerp>
index 89081ee..5559e58 100644 (file)
             </field>
         </record>
 
+        <record model="ir.ui.view" id="survey_form_invitee">
+            <field name="name">survey_form_invitee</field>
+            <field name="model">survey</field>
+            <field name="groups_id" eval="[(6, 0, [ref('base.group_survey_invitee')])]" />
+            <field name="priority" eval="1" />
+            <field name="inherit_id" ref="survey_form" />
+            <field name="arch" type="xml">
+                <data>
+                    <form position="replace">
+                        <form string="Survey" version="7.0">
+                            <sheet>
+                                <field name="state" invisible="True" />
+                                <div class="oe_button_box oe_right">
+                                    <button name="fill_survey" states="open" string="Answer Survey" type="object" icon="gtk-execute" context="{'survey_id': active_id}" attrs="{'invisible':[('state','!=','open')]}"/>
+                                </div>
+                                <div class="oe_title">
+                                    <label for="title" class="oe_edit_only"/>
+                                    <h1>
+                                        <field name="title" attrs="{'readonly':[('state','=','close')]}"/>
+                                    </h1>
+                                </div>
+                                <field name="note" />
+                            </sheet>
+                        </form>
+                    </form>
+                </data>
+            </field>
+        </record>
         <record id="survey_search" model="ir.ui.view">
             <field name="name">survey_search</field>
             <field name="model">survey</field>
             id="act_survey_pages"
             name="Pages"
             res_model="survey.page"
-            src_model="survey"/>
+            src_model="survey"
+            groups="base.group_survey_user"/>
 
         <act_window
             context="{'search_default_survey': active_id, 'default_survey': active_id}"
             id="act_survey_question"
             name="Questions"
             res_model="survey.question"
-            src_model="survey"/>
+            src_model="survey"
+            groups="base.group_survey_user"/>
 
 
         <act_window
             id="act_survey_page_question"
             name="Questions"
             res_model="survey.question"
-            src_model="survey.page"/>
+            src_model="survey.page" />
 
         <act_window domain="[('question_id', '=', active_id)]"
             id="act_survey_answer"
             id="act_survey_request"
             name="Survey Requests"
             res_model="survey.request"
-            src_model="survey"/>
+            src_model="survey"
+            groups="base.group_survey_user"/>
 
     </data>
 </openerp>
index 926810c..7b314f4 100644 (file)
@@ -82,7 +82,7 @@
     context = {'active_model':'survey', 'active_id': ref('survey_Initial_partner_feedback'), 'active_ids': [ref('survey_Initial_partner_feedback')]}
     values = self.default_get(cr, uid, ['mail_from', 'mail_subject', 'send_mail_existing', 'mail_subject_existing', 'mail', 'partner_ids', 'send_mail'], context)
     values['mail_from'] = 'Surveyor'
-    new_id = self.create(cr, uid, values)
+    new_id = self.create(cr, uid, values, context)
     self.action_send(cr, uid, [new_id], context)
 -
   I set the value in "Total start survey" field.
index fad8266..80b1491 100644 (file)
@@ -28,7 +28,7 @@ from time import strftime
 
 from openerp import addons, netsvc, tools
 from openerp.osv import fields, osv
-from openerp.tools import to_xml
+from openerp.tools import to_xml, SUPERUSER_ID
 from openerp.tools.translate import _
 from openerp.tools.safe_eval import safe_eval
 
@@ -125,7 +125,7 @@ class survey_question_wiz(osv.osv_memory):
                                 raise osv.except_osv(_('Warning!'),_("You cannot answer this survey more than %s times.") % (user_limit))
 
                         if sur_rec.max_response_limit and sur_rec.max_response_limit <= sur_rec.tot_start_survey and not sur_name_rec.page_no + 1:
-                            survey_obj.write(cr, uid, survey_id, {'state':'close', 'date_close':strftime("%Y-%m-%d %H:%M:%S")})
+                            survey_obj.write(cr, SUPERUSER_ID, survey_id, {'state':'close', 'date_close':strftime("%Y-%m-%d %H:%M:%S")})
 
                         p_id = p_id[sur_name_rec.page_no + 1]
                         surv_name_wiz.write(cr, uid, [context['sur_name_id'],], {'page_no' : sur_name_rec.page_no + 1})
@@ -401,8 +401,8 @@ class survey_question_wiz(osv.osv_memory):
                     result['fields'] = fields
                     result['context'] = context
                 else:
-                    survey_obj.write(cr, uid, survey_id, {'tot_comp_survey' : sur_rec.tot_comp_survey + 1})
-                    sur_response_obj.write(cr, uid, [sur_name_read.response], {'state' : 'done'})
+                    survey_obj.write(cr, SUPERUSER_ID, survey_id, {'tot_comp_survey' : sur_rec.tot_comp_survey + 1})
+                    sur_response_obj.write(cr, uid, int(sur_name_read.response), {'state' : 'done'})
 
                     # mark the survey request as done; call 'survey_req_done' on its actual model
                     survey_req_obj = self.pool.get(context.get('active_model'))
@@ -610,7 +610,7 @@ class survey_question_wiz(osv.osv_memory):
                                               'date': strftime('%Y-%m-%d %H:%M:%S'), 'survey_id': sur_name_read['survey_id'][0]})
             survey_id = sur_name_read['survey_id'][0]
             sur_rec = survey_obj.read(cr, uid, survey_id)
-            survey_obj.write(cr, uid, survey_id,  {'tot_start_survey' : sur_rec['tot_start_survey'] + 1})
+            survey_obj.write(cr, SUPERUSER_ID, survey_id,  {'tot_start_survey' : sur_rec['tot_start_survey'] + 1})
             if context.has_key('cur_id'):
                 if context.has_key('request') and context.get('request',False):
                     self.pool.get(context.get('object',False)).write(cr, uid, [int(context.get('cur_id',False))], {'response' : response_id})
index 177815c..b2175b4 100644 (file)
@@ -60,6 +60,7 @@ class survey_send_invitation(osv.osv_memory):
         survey_obj = self.pool.get('survey')
         msg = ""
         name = ""
+        survey_id = 0
         for sur in survey_obj.browse(cr, uid, context.get('active_ids', []), context=context):
             name += "\n --> " + sur.title + "\n"
             if sur.state != 'open':
@@ -67,6 +68,7 @@ class survey_send_invitation(osv.osv_memory):
             data['mail_subject'] = _("Invitation for %s") % (sur.title)
             data['mail_subject_existing'] = _("Invitation for %s") % (sur.title)
             data['mail_from'] = sur.responsible_id.email
+            survey_id = sur.id
         if msg:
             raise osv.except_osv(_('Warning!'), _('The following surveys are not in open state: %s') % msg)
         data['mail'] = _('''
@@ -77,7 +79,12 @@ You can access this survey with the following parameters:
  Your login ID: %%(login)s\n
  Your password: %%(passwd)s\n
 \n\n
-Thanks,''') % (name, self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://localhost:8069', context=context))
+Thanks,''') % (
+            name, 
+            self.pool.get('ir.config_parameter').get_param(
+                cr, uid, 'web.base.url', default='http://localhost:8069',
+                context=context)
+                + '#id=%d&view_type=form&model=survey' % survey_id)
         return data
 
     def create_report(self, cr, uid, res_ids, report_name=False, file_name=False):
@@ -108,8 +115,8 @@ Thanks,''') % (name, self.pool.get('ir.config_parameter').get_param(cr, uid, 'we
         mail_message = self.pool.get('mail.message')
 
         model_data_obj = self.pool.get('ir.model.data')
-        group_id = model_data_obj._get_id(cr, uid, 'base', 'group_survey_user')
-        group_id = model_data_obj.browse(cr, uid, group_id, context=context).res_id
+        group_id = model_data_obj.get_object_reference(
+                cr, uid, 'base', 'group_survey_invitee')[1]
 
         act_id = self.pool.get('ir.actions.act_window')
         act_id = act_id.search(cr, uid, [('res_model', '=' , 'survey.name.wiz'), \
@@ -183,7 +190,9 @@ Thanks,''') % (name, self.pool.get('ir.config_parameter').get_param(cr, uid, 'we
                                 'address_id': partner.id,
                                 'groups_id': [[6, 0, [group_id]]],
                                 'action_id': act_id[0],
-                                'survey_id': [[6, 0, survey_ids]]
+                                'survey_id': [[6, 0, survey_ids]],
+                                'partner_id': partner.id,
+                                'tz': context.get('tz'),
                                }
                     user = user_ref.create(cr, uid, res_data)
                     if user not in new_user:
index a611973..4bc29d8 100644 (file)
@@ -45,7 +45,8 @@
         <act_window id="action_act_view_survey_send_invitation"
             key2="client_action_multi" name="Send Invitations"
             res_model="survey.send.invitation" src_model="survey"
-            view_mode="form" target="new" view_type="form" />
+            view_mode="form" target="new" view_type="form"
+            groups="base.group_survey_user"/>
 
         <!-- Survey send invitation Display Log Form View -->
 
index 6206d1f..18e0947 100644 (file)
@@ -1758,16 +1758,32 @@ class Reports(openerpweb.Controller):
             report = zlib.decompress(report)
         report_mimetype = self.TYPES_MAPPING.get(
             report_struct['format'], 'octet-stream')
-        file_name = action.get('name', 'report')
-        if 'name' not in action:
-            reports = req.session.model('ir.actions.report.xml')
-            res_id = reports.search([('report_name', '=', action['report_name']),],
-                                    0, False, False, context)
-            if len(res_id) > 0:
-                file_name = reports.read(res_id[0], ['name'], context)['name']
-            else:
-                file_name = action['report_name']
+        file_name = action['report_name']
+        # Try to get current object model and their ids from context
+        if 'context' in action:
+            action_context = action['context']
+            if (action_context.get('active_model')
+                    and action_context['active_ids']):
+                # Use built-in ORM method to get data from DB
+                m = req.session.model(action_context['active_model'])
+                r = []
+                try:
+                    r = m.name_get(action_context['active_ids'], context)
+                except xmlrpclib.Fault:
+                    #we assume this went wrong because of incorrect/missing
+                    #_rec_name. We don't have access to _columns here to do
+                    # a proper check
+                    pass
+                # Parse result to create a better filename
+                item_names = [item[1] or str(item[0]) for item in r]
+                if action.get('name'):
+                    item_names.insert(0, action['name'])
+                if item_names:
+                    file_name = '-'.join(item_names)[:251]
         file_name = '%s.%s' % (file_name, report_struct['format'])
+        # Create safe filename
+        p = re.compile('[/:(")<>|?*]|(\\\)')
+        file_name = p.sub('_', file_name)
 
         return req.make_response(report,
              headers=[
index 2c439c3..d65661f 100644 (file)
   position: relative;
   display: inline-block;
 }
+.openerp .oe_form_editable .oe_form_required.oe_form_field_one2many table.oe_list_content, .openerp .oe_form_editable .oe_form_required.oe_form_field_many2many table.oe_list_content {
+  background-color: #d2d2ff !important;
+}
+.openerp .oe_form_editable .oe_form_invalid.oe_form_field_one2many table.oe_list_content, .openerp .oe_form_editable .oe_form_invalid.oe_form_field_many2many table.oe_list_content {
+  background-color: #ff6666 !important;
+}
 .openerp .oe_form_invalid input, .openerp .oe_form_invalid select, .openerp .oe_form_invalid textarea {
   background-color: #F66 !important;
   border: 1px solid #D00 !important;
 }
 .openerp .oe_form_sheet_width {
   min-width: 650px;
-  max-width: 860px;
-  margin: 0 auto;
+  max-width: auto;
+  margin: 16px;
 }
 .openerp .oe_form_sheet {
   background: white;
 .openerp .oe_form div.oe_chatter {
   box-sizing: border-box;
   min-width: 682px;
-  max-width: 892px;
-  margin: 0 auto;
+  max-width: 0 auto;
+  margin: 16px;
   padding: 16px 16px 48px;
 }
 .openerp .oe_form div.oe_form_configuration p, .openerp .oe_form div.oe_form_configuration ul, .openerp .oe_form div.oe_form_configuration ol {
 .openerp .oe_form .oe_form_field_one2many > .oe_view_manager .oe_list_pager_single_page {
   display: none;
 }
+.openerp .oe_form_field_one2many, .openerp .oe_form_field_many2many {
+  overflow-x: auto;
+}
 .openerp .oe_form_field_one2many > .oe_view_manager .oe_list_pager_single_page, .openerp .oe_form_field_many2many > .oe_view_manager .oe_list_pager_single_page {
   display: none !important;
 }
index 65119df..7b6f0a6 100644 (file)
@@ -9,7 +9,7 @@ $tag-border: #afafb6
 $tag-border-selected: #a6a6fe
 $hover-background: #f0f0fa
 $link-color: #7C7BAD
-$sheet-max-width: 860px
+$sheet-max-width: auto
 $sheet-min-width: 650px
 $sheet-padding: 16px
 // }}}
@@ -1648,6 +1648,15 @@ $sheet-padding: 16px
     .oe_form_dropdown_section
         position: relative
         display: inline-block
+    .oe_form_editable
+        .oe_form_required
+            &.oe_form_field_one2many, &.oe_form_field_many2many
+                table.oe_list_content
+                    background-color: #d2d2ff !important
+        .oe_form_invalid
+            &.oe_form_field_one2many, &.oe_form_field_many2many
+                table.oe_list_content
+                    background-color: #ff6666 !important
     .oe_form_invalid
         input, select, textarea
             background-color: #F66 !important
@@ -1687,7 +1696,7 @@ $sheet-padding: 16px
     .oe_form_sheet_width
         min-width: 650px
         max-width: $sheet-max-width
-        margin: 0 auto
+        margin: 16px
     .oe_form_sheet
         background: white
         min-height: 330px
@@ -1734,8 +1743,8 @@ $sheet-padding: 16px
         div.oe_chatter
             box-sizing: border-box
             min-width: $sheet-min-width + 2* $sheet-padding
-            max-width: $sheet-max-width + 2* $sheet-padding
-            margin: 0 auto
+            max-width: 0 auto
+            margin: 16px
             padding: 16px 16px 48px
         div.oe_form_configuration
             p, ul, ol
@@ -2168,6 +2177,7 @@ $sheet-padding: 16px
             display: none
     .oe_form_field_one2many,.oe_form_field_many2many
         // TODO: oe_form_field_one2many_list?
+        overflow-x: auto
         > .oe_view_manager
             .oe_list_pager_single_page
                 display: none !important
@@ -2628,7 +2638,7 @@ div.ui-widget-overlay
                     filter: alpha(opacity = 0)
                     border: none
                     width: 0
-                    border-right: none 
+                    border-right: none
         .label
             border-bottom: 1px solid #cacaca
             background: transparent
index 5553572..394a936 100644 (file)
@@ -682,7 +682,7 @@ instance.web.DataSetStatic =  instance.web.DataSet.extend({
         var offset = options.offset || 0,
             limit = options.limit || false;
         var end_pos = limit && limit !== -1 ? offset + limit : this.ids.length;
-        return this.read_ids(this.ids.slice(offset, end_pos), fields);
+        return this.read_ids(this.ids.slice(offset, end_pos), fields, options);
     },
     set_ids: function (ids) {
         this.ids = ids;
index be16faa..195b672 100644 (file)
@@ -148,6 +148,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
             this.$el.off('.formBlur');
         }
         this._super();
+        if (this.$buttons) {
+            this.$buttons.remove();
+        }
     },
     load_form: function(data) {
         var self = this;
@@ -3514,7 +3517,6 @@ var commands = {
 };
 instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
     multi_selection: false,
-    disable_utility_classes: true,
     init: function(field_manager, node) {
         this._super(field_manager, node);
         lazy_build_o2m_kanban_view();
@@ -3784,6 +3786,9 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
         }
         return $.when(false);
     },
+    is_false: function() {
+        return this.dataset.ids.length == 0;
+    },
     is_syntax_valid: function() {
         if (! this.viewmanager || ! this.viewmanager.views[this.viewmanager.active_view])
             return true;
@@ -3851,6 +3856,10 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({
 instance.web.form.One2ManyDataSet = instance.web.BufferedDataSet.extend({
     get_context: function() {
         this.context = this.o2m.build_context();
+        var self = this;
+        _.each(arguments, function(context) {
+            self.context.add(context);
+        });
         return this.context;
     }
 });
@@ -4288,7 +4297,6 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
 */
 instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
     multi_selection: false,
-    disable_utility_classes: true,
     init: function(field_manager, node) {
         this._super(field_manager, node);
         this.is_loaded = $.Deferred();
index 52124d5..d7d7ef7 100644 (file)
@@ -2260,13 +2260,9 @@ instance.web.list.Binary = instance.web.list.Column.extend({
         var text = _t("Download");
         var value = row_data[this.id].value;
         var download_url;
-        if (value && value.substr(0, 10).indexOf(' ') == -1) {
-            download_url = "data:application/octet-stream;base64," + value;
-        } else {
-            download_url = instance.session.url('/web/binary/saveas', {model: options.model, field: this.id, id: options.id});
-            if (this.filename) {
-                download_url += '&filename_field=' + this.filename;
-            }
+        download_url = instance.session.url('/web/binary/saveas', {model: options.model, field: this.id, id: options.id});
+        if (this.filename) {
+            download_url += '&filename_field=' + this.filename;
         }
         if (this.filename && row_data[this.filename]) {
             text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
index 5fa3c8f..9fcfe07 100644 (file)
@@ -365,6 +365,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
      * @return {*}
      */
     ir_actions_common: function(executor, options) {
+        var self = this;
         if (this.inner_widget && executor.action.target !== 'new') {
             if (this.getParent().has_uncommitted_changes()) {
                 return $.Deferred().reject();
index 2c7edaf..fa134c5 100644 (file)
@@ -549,7 +549,7 @@ instance.web_calendar.CalendarView = instance.web.View.extend({
         var self = this;
         var index = this.dataset.get_id_index(event_id);
         if (index !== null) {
-            this.dataset.unlink(this.dataset.ids[index]);
+            this.dataset.unlink([this.dataset.ids[index]]);
         }
     },
 });
index 23fa492..05190d1 100644 (file)
@@ -431,8 +431,6 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
                 record.do_reload();
                 new_group.do_save_sequences();
             }).fail(function(error, evt) {
-                evt.preventDefault();
-                alert(_t("An error has occured while moving the record to this group: ") + data.fault_code);
                 self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails
             });
         }
@@ -644,12 +642,10 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
     },
     do_show_more: function(evt) {
         var self = this;
-        var ids = self.view.dataset.ids.splice(0);
         return this.dataset.read_slice(this.view.fields_keys.concat(['__last_update']), {
             'limit': self.view.limit,
             'offset': self.dataset_offset += self.view.limit
         }).then(function(records) {
-            self.view.dataset.ids = ids.concat(self.dataset.ids);
             self.do_add_records(records);
             self.compute_cards_auto_height();
             self.view.postprocess_m2m_tags();
index 996b0f6..4ec4227 100644 (file)
@@ -179,11 +179,12 @@ class ir_attachment(osv.osv):
     }
 
     def _auto_init(self, cr, context=None):
-        super(ir_attachment, self)._auto_init(cr, context)
+        result = super(ir_attachment, self)._auto_init(cr, context)
         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',))
         if not cr.fetchone():
             cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)')
             cr.commit()
+        return result
 
     def check(self, cr, uid, ids, mode, context=None, values=None):
         """Restricts the access to an ir.attachment, according to referred model
index 00d18d0..ad803a7 100644 (file)
@@ -62,7 +62,7 @@ class res_partner_bank_type(osv.osv):
         'name': fields.char('Name', size=64, required=True, translate=True),
         'code': fields.char('Code', size=64, required=True),
         'field_ids': fields.one2many('res.partner.bank.type.field', 'bank_type_id', 'Type Fields'),
-        'format_layout': fields.text('Format Layout', translate=True)
+        'format_layout': fields.text('Format Layout', translate=False)
     }
     _defaults = {
         'format_layout': lambda *args: "%(bank_name)s: %(acc_number)s"
@@ -139,6 +139,7 @@ class res_partner_bank(osv.osv):
         'state': fields.selection(_bank_type_get, 'Bank Account Type', required=True,
             change_default=True),
         'sequence': fields.integer('Sequence'),
+        'active': fields.boolean('Active', select=True),
         'footer': fields.boolean("Display on Reports", help="Display this bank account on the footer of printed documents like invoices and sales orders.")
     }
 
@@ -155,7 +156,8 @@ class res_partner_bank(osv.osv):
             cursor, user, 'country_id', context=context),
         'state_id': lambda obj, cursor, user, context: obj._default_value(
             cursor, user, 'state_id', context=context),
-        'name': '/'
+        'name': '/',
+        'active': True,
     }
 
     def fields_get(self, cr, uid, allfields=None, context=None):
index 7c1e07e..a7b90a3 100644 (file)
@@ -97,6 +97,8 @@
                     <group col="4">
                         <field name="state"/>
                         <field name="acc_number" placeholder="Account Number"/>
+                        <field name="active"/>
+                        <newline/>
                         <field name="company_id" groups="base.group_multi_company" on_change="onchange_company_id(company_id)"
                             invisible="context.get('company_hide', True)" widget="selection"/>
                         <field name="footer" invisible="context.get('footer_hide', True)"/>
index 2e22687..f684241 100644 (file)
         <record id="pl" model="res.country">
             <field name="name">Poland</field>
             <field name="code">pl</field>
-            <field name="currency_id" ref="PLZ"/>
+            <field name="currency_id" ref="PLN"/>
         </record>
         <record id="pm" model="res.country">
             <field name="name">Saint Pierre and Miquelon</field>
index 78bb972..0acb252 100644 (file)
@@ -148,7 +148,7 @@ class lang(osv.osv):
         'direction': 'ltr',
         'date_format':_get_default_date_format,
         'time_format':_get_default_time_format,
-        'grouping': '[]',
+        'grouping': '[3, 0]',
         'decimal_point': '.',
         'thousands_sep': ',',
     }
index 22cd4a8..4af0d13 100644 (file)
@@ -28,7 +28,7 @@ import re
 import openerp
 from openerp import SUPERUSER_ID
 from openerp import pooler, tools
-from openerp.osv import osv, fields
+from openerp.osv import osv, fields, orm
 from openerp.osv.expression import get_unaccent_wrapper
 from openerp.tools.translate import _
 from openerp.tools.yaml_import import is_comment
@@ -81,27 +81,29 @@ def _tz_get(self,cr,uid, context=None):
 class res_partner_category(osv.osv):
 
     def name_get(self, cr, uid, ids, context=None):
-        """Return the categories' display name, including their direct
-           parent by default.
-
-        :param dict context: the ``partner_category_display`` key can be
-                             used to select the short version of the
-                             category name (without the direct parent),
-                             when set to ``'short'``. The default is
-                             the long version."""
+        """ Return the categories' display name, including their direct
+            parent by default.
+
+            If ``context['partner_category_display']`` is ``'short'``, the short
+            version of the category name (without the direct parent) is used.
+            The default is the long version.
+        """
+        if not isinstance(ids, list):
+            ids = [ids]
         if context is None:
             context = {}
+
         if context.get('partner_category_display') == 'short':
             return super(res_partner_category, self).name_get(cr, uid, ids, context=context)
-        if isinstance(ids, (int, long)):
-            ids = [ids]
-        reads = self.read(cr, uid, ids, ['name', 'parent_id'], context=context)
+
         res = []
-        for record in reads:
-            name = record['name']
-            if record['parent_id']:
-                name = record['parent_id'][1] + ' / ' + name
-            res.append((record['id'], name))
+        for category in self.browse(cr, uid, ids, context=context):
+            names = []
+            current = category
+            while current:
+                names.append(current.name)
+                current = current.parent_id
+            res.append((category.id, ' / '.join(reversed(names))))
         return res
 
     def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
@@ -713,6 +715,7 @@ class res_partner(osv.osv, format_address):
             adr_pref.add('default')
         result = {}
         visited = set()
+        partner = orm.browse_null()
         for partner in self.browse(cr, uid, filter(None, ids), context=context):
             current_partner = partner
             while current_partner:
index 5ee4029..44a3d4a 100644 (file)
@@ -197,13 +197,21 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
                 # 'data' section, but should probably not alter the data,
                 # as there is no rollback.
                 if tools.config.options['test_enable']:
-                    report.record_result(load_test(module_name, idref, mode))
-
+                    report.record_result(load_test(module_name, idref, mode),
+                                         details=(dict(module=module_name,
+                                                       msg="Exception during load of legacy "
+                                                       "data-based tests (yml...)")))
                     # Run the `fast_suite` and `checks` tests given by the module.
                     if module_name == 'base':
                         # Also run the core tests after the database is created.
-                        report.record_result(openerp.modules.module.run_unit_tests('openerp'))
-                    report.record_result(openerp.modules.module.run_unit_tests(module_name))
+                        report.record_result(openerp.modules.module.run_unit_tests('openerp'),
+                                             details=dict(module='openerp',
+                                                          msg="Failure or error in server core "
+                                                          "unit tests"))
+                    report.record_result(openerp.modules.module.run_unit_tests(module_name),
+                                         details=dict(module=module_name,
+                                                      msg="Failure or error in unit tests, "
+                                                      "check logs for more details"))
 
             processed_modules.append(package.name)
 
index dc949db..73821db 100644 (file)
@@ -2277,6 +2277,7 @@ class BaseModel(object):
                 view = getattr(self, '_get_default_%s_view' % view_type)(
                     cr, user, context)
             except AttributeError:
+                if config['debug_mode']: raise
                 # what happens here, graph case?
                 raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type)
 
@@ -3307,7 +3308,8 @@ class BaseModel(object):
 
         cr.commit()     # start a new transaction
 
-        self._add_sql_constraints(cr)
+        if getattr(self, '_auto', True):
+            self._add_sql_constraints(cr)
 
         if create:
             self._execute_sql(cr)
index d870b0f..5233533 100644 (file)
@@ -35,6 +35,7 @@ import openerp.sql_db as sql_db
 from openerp.tools.translate import translate
 from openerp.osv.orm import MetaModel, Model, TransientModel, AbstractModel
 import openerp.exceptions
+from openerp.tools.config import config
 
 import time
 import random
@@ -142,11 +143,13 @@ class object_proxy(object):
                     _logger.info("%s, retry %d/%d in %.04f sec..." % (errorcodes.lookup(e.pgcode), tries, MAX_TRIES_ON_CONCURRENCY_FAILURE, wait_time))
                     time.sleep(wait_time)
                 except orm.except_orm, inst:
+                    if config['debug_mode']: raise
                     _, _, tb = sys.exc_info()
                     raise except_osv(inst.name, inst.value), None, tb
                 except except_osv:
                     raise
                 except IntegrityError, inst:
+                    if config['debug_mode']: raise
                     osv_pool = pooler.get_pool(dbname)
                     for key in osv_pool._sql_error.keys():
                         if key in inst[0]:
index 3b36c3e..c01ff03 100644 (file)
@@ -767,8 +767,17 @@ class _rml_flowable(object):
             if extra_style:
                 style.__dict__.update(extra_style)
             result = []
-            for i in self._textual(node).split('\n'):
-                result.append(platypus.Paragraph(i, style, **(utils.attr_get(node, [], {'bulletText':'str'}))))
+            textuals = self._textual(node).split('\n')
+            keep_empty_lines = (len(textuals) > 1) and len(node.text.strip())
+            for i in textuals:
+                if keep_empty_lines and len(i.strip()) == 0:
+                    i = '<font color="white">&nbsp;</font>'
+                result.append(
+                    platypus.Paragraph(
+                        i, style, **(
+                            utils.attr_get(node, [], {'bulletText':'str'}))
+                    )
+                )
             return result
         elif node.tag=='barCode':
             try:
index 954ef84..94b3d9d 100644 (file)
@@ -86,7 +86,7 @@ def _child_get(node, self=None, tagname=None):
                 n2.tag = tag
                 n2.attrib.update(attr or {})
                 yield n2
-                tagname = ''
+                continue
             except GeneratorExit:
                 pass
             except Exception, e:
index db64b62..cfb07c9 100644 (file)
@@ -269,7 +269,7 @@ class Worker(object):
         r = resource.getrusage(resource.RUSAGE_SELF)
         cpu_time = r.ru_utime + r.ru_stime
         def time_expired(n, stack):
-            _logger.info('Worker (%d) CPU time limit (%s) reached.', config['limit_time_cpu'])
+            _logger.info('Worker (%d) CPU time limit (%s) reached.', os.getpid(), config['limit_time_cpu'])
             # We dont suicide in such case
             raise Exception('CPU time limit exceeded.')
         signal.signal(signal.SIGXCPU, time_expired)
index 23eb6a9..4076c64 100644 (file)
@@ -79,7 +79,22 @@ def xmlrpc_return(start_response, service, method, params, legacy_exceptions=Fal
     # This also mimics SimpleXMLRPCDispatcher._marshaled_dispatch() for
     # exception handling.
     try:
-        result = openerp.netsvc.dispatch_rpc(service, method, params)
+        def fix(res):
+            """
+            This fix is a minor hook to avoid xmlrpclib to raise TypeError exception: 
+            - To respect the XML-RPC protocol, all "int" and "float" keys must be cast to string to avoid
+              TypeError, "dictionary key must be string"
+            - And since "allow_none" is disabled, we replace all None values with a False boolean to avoid
+              TypeError, "cannot marshal None unless allow_none is enabled"
+            """
+            if res is None:
+                return False
+            elif type(res) == dict:
+                return dict((str(key), fix(value)) for key, value in res.items())
+            else:
+                return res
+            
+        result = fix(openerp.netsvc.dispatch_rpc(service, method, params))
         response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False, encoding=None)
     except Exception, e:
         if legacy_exceptions:
index 60606d1..64db017 100644 (file)
@@ -8,20 +8,28 @@ class assertion_report(object):
     def __init__(self):
         self.successes = 0
         self.failures = 0
+        self.failures_details = []
 
     def record_success(self):
         self.successes += 1
 
-    def record_failure(self):
+    def record_failure(self, details=None):
         self.failures += 1
+        if details is not None:
+            self.failures_details.append(details)
 
-    def record_result(self, result):
+    def record_result(self, result, details=None):
+        """Record either success or failure, with the provided details in the latter case.
+
+        :param result: a boolean
+        :param details: a dict with keys ``'module'``, ``'testfile'``, ``'msg'``, ``'msg_args'``
+        """
         if result is None:
             pass
         elif result is True:
             self.record_success()
         elif result is False:
-            self.record_failure()
+            self.record_failure(details=details)
 
     def __str__(self):
         res = 'Assertions report: %s successes, %s failures' % (self.successes, self.failures)
index bd7c214..3fafa55 100644 (file)
@@ -697,13 +697,15 @@ form: module.record_id""" % (xml_id,)
             if rec_src_count:
                 count = int(rec_src_count)
                 if len(ids) != count:
-                    self.assertion_report.record_failure()
                     msg = 'assertion "%s" failed!\n'    \
                           ' Incorrect search count:\n'  \
                           ' expected count: %d\n'       \
-                          ' obtained count: %d\n'       \
-                          % (rec_string, count, len(ids))
-                    _logger.error(msg)
+                          ' obtained count: %d\n'
+                    msg_args = (rec_string, count, len(ids))
+                    _logger.error(msg, msg_args)
+                    self.assertion_report.record_failure(details=dict(module=self.module,
+                                                                      msg=msg,
+                                                                      msg_args=msg_args))
                     return
 
         assert ids is not None,\
@@ -725,13 +727,15 @@ form: module.record_id""" % (xml_id,)
                 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
                 expression_value = unsafe_eval(f_expr, globals_dict)
                 if expression_value != expected_value: # assertion failed
-                    self.assertion_report.record_failure()
                     msg = 'assertion "%s" failed!\n'    \
                           ' xmltag: %s\n'               \
                           ' expected value: %r\n'       \
-                          ' obtained value: %r\n'       \
-                          % (rec_string, etree.tostring(test), expected_value, expression_value)
-                    _logger.error(msg)
+                          ' obtained value: %r\n'
+                    msg_args = (rec_string, etree.tostring(test), expected_value, expression_value)
+                    self.assertion_report.record_failure(details=dict(module=self.module,
+                                                                      msg=msg,
+                                                                      msg_args=msg_args))
+                    _logger.error(msg, msg_args)
                     return
         else: # all tests were successful for this assertion tag (no break)
             self.assertion_report.record_success()
index 0caecdf..90f1282 100644 (file)
@@ -465,7 +465,7 @@ def trans_export(lang, modules, buffer, format, cr):
                 row.setdefault('tnrs', []).append((type, name, res_id))
                 row.setdefault('comments', set()).update(comments)
 
-            for src, row in grouped_rows.items():
+            for src, row in sorted(grouped_rows.items()):
                 if not lang:
                     # translation template, so no translation value
                     row['translation'] = ''
@@ -785,6 +785,11 @@ def trans_generate(lang, modules, cr):
                 except (IOError, etree.XMLSyntaxError):
                     _logger.exception("couldn't export translation for report %s %s %s", name, report_type, fname)
 
+        elif model == 'ir.model':
+            model_pool = pool.get(obj.model)
+            if model_pool:
+                push_translation(module, 'code', '_description', 0, model_pool._description)
+
         for field_name,field_def in obj._table._columns.items():
             if field_def.translate:
                 name = model + "," + field_name
index b065294..6dc1298 100644 (file)
@@ -1,10 +1,12 @@
 # -*- coding: utf-8 -*-
+import os
 import threading
 import types
 import time # used to eval time.strftime expressions
 from datetime import datetime, timedelta
 import logging
 
+from copy import deepcopy
 import openerp.pooler as pooler
 import openerp.sql_db as sql_db
 import misc
@@ -193,7 +195,13 @@ class YamlInterpreter(object):
         return node
 
     def _log_assert_failure(self, msg, *args):
-        self.assertion_report.record_failure()
+        from openerp.modules import module  # cannot be made before (loop)
+        basepath = module.get_module_path(self.module)
+        self.assertion_report.record_failure(
+            details=dict(module=self.module,
+                         testfile=os.path.relpath(self.filename, basepath),
+                         msg=msg,
+                         msg_args=deepcopy(args)))
         _logger.error(msg, *args)
 
     def _get_assertion_id(self, assertion):