[MERGE] Forward-port 8.0 up to d706adb
authorOlivier Dony <odo@openerp.com>
Sat, 2 Aug 2014 19:13:41 +0000 (21:13 +0200)
committerOlivier Dony <odo@openerp.com>
Sun, 3 Aug 2014 23:47:07 +0000 (01:47 +0200)
147 files changed:
addons/account/account_invoice.py
addons/account/res_config.py
addons/account/res_config_view.xml
addons/account/test/account_fiscalyear_close.yml
addons/account/test/account_supplier_invoice.yml
addons/account/test/test_edi_invoice.yml
addons/account_analytic_analysis/test/account_analytic_analysis.yml
addons/account_anglo_saxon/test/anglo_saxon.yml
addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml
addons/account_bank_statement_import/__init__.py [new file with mode: 0644]
addons/account_bank_statement_import/__openerp__.py [new file with mode: 0644]
addons/account_bank_statement_import/account_bank_statement_import.py [new file with mode: 0644]
addons/account_bank_statement_import/account_bank_statement_import_view.xml [new file with mode: 0644]
addons/account_bank_statement_import/demo/fiscalyear_period.xml [new file with mode: 0644]
addons/account_bank_statement_import/demo/partner_bank.xml [new file with mode: 0644]
addons/account_bank_statement_import_ofx/__init__.py [new file with mode: 0644]
addons/account_bank_statement_import_ofx/__openerp__.py [new file with mode: 0644]
addons/account_bank_statement_import_ofx/account_bank_statement_import_ofx.py [new file with mode: 0644]
addons/account_bank_statement_import_ofx/test_ofx_file/test_ofx.ofx [new file with mode: 0644]
addons/account_bank_statement_import_ofx/tests/__init__.py [new file with mode: 0644]
addons/account_bank_statement_import_ofx/tests/test_import_bank_statement.py [new file with mode: 0644]
addons/account_bank_statement_import_qif/__init__.py [new file with mode: 0644]
addons/account_bank_statement_import_qif/__openerp__.py [new file with mode: 0644]
addons/account_bank_statement_import_qif/account_bank_statement_import_qif.py [new file with mode: 0644]
addons/account_bank_statement_import_qif/test_qif_file/test_qif.qif [new file with mode: 0644]
addons/account_bank_statement_import_qif/tests/__init__.py [new file with mode: 0644]
addons/account_bank_statement_import_qif/tests/test_import_bank_statement.py [new file with mode: 0644]
addons/account_voucher/test/case1_usd_usd.yml
addons/account_voucher/test/case1_usd_usd_payment_rate.yml
addons/account_voucher/test/case2_suppl_usd_eur.yml
addons/account_voucher/test/case2_usd_eur_debtor_in_eur.yml
addons/account_voucher/test/case2_usd_eur_debtor_in_usd.yml
addons/account_voucher/test/case3_eur_eur.yml
addons/account_voucher/test/case4_cad_chf.yml
addons/account_voucher/test/case5_suppl_usd_usd.yml
addons/account_voucher/test/case_eur_usd.yml
addons/base_geolocalize/views/res_partner_view.xml
addons/crm_partner_assign/crm_partner_assign.py
addons/crm_partner_assign/res_partner_view.xml
addons/delivery/test/delivery_cost.yml
addons/event/__init__.py
addons/event/__openerp__.py
addons/event/res_config.py [new file with mode: 0644]
addons/event/res_config_view.xml [new file with mode: 0644]
addons/fleet/fleet.py
addons/fleet/fleet_view.xml
addons/hr/hr.py
addons/hr/hr_data.xml
addons/hr/hr_view.xml
addons/hr/res_users.py
addons/hr_attendance/hr_attendance_view.xml
addons/hr_contract/hr_contract.py
addons/hr_contract/hr_contract_data.xml
addons/hr_contract/hr_contract_view.xml
addons/hr_evaluation/hr_evaluation.py
addons/hr_evaluation/hr_evaluation_data.xml
addons/hr_evaluation/hr_evaluation_view.xml
addons/hr_expense/hr_expense.py
addons/hr_expense/hr_expense_data.xml
addons/hr_expense/hr_expense_view.xml
addons/hr_holidays/hr_holidays.py
addons/hr_holidays/hr_holidays_data.xml
addons/hr_holidays/hr_holidays_view.xml
addons/hr_recruitment/hr_recruitment.py
addons/hr_recruitment/hr_recruitment_data.xml
addons/hr_recruitment/hr_recruitment_view.xml
addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml
addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice_no_prod_tax.yml
addons/hr_timesheet_sheet/hr_timesheet_sheet.py
addons/hr_timesheet_sheet/hr_timesheet_sheet_data.xml
addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
addons/l10n_be_coda/__openerp__.py
addons/l10n_be_coda/l10n_be_coda_view.xml
addons/l10n_be_coda/l10n_be_coda_wizard.xml [deleted file]
addons/l10n_be_coda/test_coda_file/Ontvangen_CODA.2011-01-11-18.59.15.txt [deleted file]
addons/l10n_be_coda/test_coda_file/Ontvangen_CODA.2013-01-11-18.59.15.txt [new file with mode: 0644]
addons/l10n_be_coda/tests/__init__.py [new file with mode: 0644]
addons/l10n_be_coda/tests/test_import_bank_statement.py [new file with mode: 0644]
addons/l10n_be_coda/wizard/account_coda_import.py
addons/mail/__openerp__.py
addons/mail/mail_mail.py
addons/mail/mail_mail_view.xml
addons/mail/res_config.py
addons/mail/res_config_view.xml
addons/mail/static/src/css/mail.css
addons/mail/tests/test_mail_features.py
addons/marketing/res_config_view.xml
addons/mass_mailing/views/website_mass_mailing.xml
addons/point_of_sale/controllers/main.py
addons/point_of_sale/report/pos_order_report.py
addons/point_of_sale/res_partner_view.xml
addons/point_of_sale/test/02_order_to_invoice.yml
addons/point_of_sale/views/templates.xml
addons/product/partner_view.xml
addons/product/test/product_pricelist.yml
addons/project/project.py
addons/project/res_config.py
addons/project/res_config_view.xml
addons/purchase/test/process/edi_purchase_order.yml
addons/purchase/test/process/rfq2order2done.yml
addons/purchase_requisition/test/purchase_requisition.yml
addons/resource/test/resource.yml
addons/sale/sale.py
addons/sale/sale_demo.xml
addons/sale/sale_view.xml
addons/sale/test/edi_sale_order.yml
addons/sale/test/sale_order_demo.yml
addons/sale_crm/sale_crm_view.xml
addons/sale_journal/sale_journal_view.xml
addons/sale_stock/test/picking_order_policy.yml
addons/sale_stock/test/sale_order_onchange.yml
addons/stock/controllers/main.py
addons/stock/doc/stock.rst
addons/stock/partner_view.xml
addons/stock/static/src/js/widgets.js
addons/stock/stock.py
addons/stock/views/stock.xml
addons/web/static/src/js/openerpframework.js
addons/web/static/src/js/tour.js
addons/website/controllers/main.py
addons/website/models/ir_ui_view.py
addons/website/static/src/css/editor.css
addons/website/static/src/css/editor.sass
addons/website/static/src/js/website.ace.js
addons/website/static/src/js/website.js
addons/website/static/src/xml/website.ace.xml
addons/website/static/src/xml/website.seo.xml
addons/website/views/website_templates.xml
addons/website_blog/static/src/js/website_blog.inline.discussion.js
addons/website_blog/views/website_blog_templates.xml
addons/website_crm_partner_assign/models/res_partner.py
addons/website_event_track/static/src/css/website_event_track.css
addons/website_event_track/static/src/js/website_event_track.js
addons/website_event_track/views/website_event.xml
addons/website_forum/models/forum.py
addons/website_forum/views/res_users.xml
addons/website_forum/views/website_forum.xml
addons/website_partner/models/res_partner.py
addons/website_partner/views/res_partner_view.xml
addons/website_quote/controllers/main.py
addons/website_quote/views/website_quotation.xml
addons/website_quote/views/website_quotation_backend.xml
openerp/addons/base/ir/ir_qweb.py
openerp/addons/base/res/res_partner.py
openerp/addons/base/res/res_partner_view.xml
openerp/cli/__init__.py
openerp/release.py

index e509b7e..a8be837 100644 (file)
@@ -712,7 +712,7 @@ class account_invoice(models.Model):
             for tax in self.tax_line:
                 if tax.manual:
                     continue
-                key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id, tax.account_analytic_id.id)
+                key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
                 tax_key.append(key)
                 if key not in compute_taxes:
                     raise except_orm(_('Warning!'), _('Global taxes defined, but they are not in invoice lines !'))
@@ -1554,7 +1554,15 @@ class account_invoice_tax(models.Model):
                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
                     val['account_analytic_id'] = tax['account_analytic_paid_id']
 
-                key = (val['tax_code_id'], val['base_code_id'], val['account_id'], val['account_analytic_id'])
+                # If the taxes generate moves on the same financial account as the invoice line
+                # and no default analytic account is defined at the tax level, propagate the
+                # analytic account from the invoice line to the tax line. This is necessary
+                # in situations were (part of) the taxes cannot be reclaimed,
+                # to ensure the tax move is allocated to the proper analytic account.
+                if not val.get('account_analytic_id') and line.account_analytic_id and val['account_id'] == line.account_id.id:
+                    val['account_analytic_id'] = line.account_analytic_id.id
+
+                key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
                 if not key in tax_grouped:
                     tax_grouped[key] = val
                 else:
index 5fd111a..f729f80 100644 (file)
@@ -108,6 +108,12 @@ class account_config_settings(osv.osv_memory):
         'module_product_email_template': fields.boolean('Send products tools and information at the invoice confirmation',
             help='With this module, link your products to a template to send complete information and tools to your customer.\n'
                  'For instance when invoicing a training, the training agenda and materials will automatically be send to your customers.'),
+        'module_account_bank_statement_import_ofx': fields.boolean('Import of Bank Statements in .OFX Format',
+            help='Get your bank statements from you bank and import them in Odoo in .OFX format.\n'
+                '-that installs the module account_bank_statement_import.'),
+        'module_account_bank_statement_import_qif': fields.boolean('Import of Bank Statements in .QIF Format.',
+            help='Get your bank statements from you bank and import them in Odoo in .QIF format.\n'
+                '-that installs the module account_bank_statement_import_qif.'),
         'group_proforma_invoices': fields.boolean('Allow pro-forma invoices',
             implied_group='account.group_proforma_invoices',
             help="Allows you to put invoices in pro-forma state."),
index b33264f..5e43fd1 100644 (file)
                                 <field name="paypal_account" placeholder="e.g. sales@odoo.com" class="oe_inline"/>
                             </div>
                         </div>
+                        <label for="id" string="Bank Statements"/>
+                        <div>
+                            <div>
+                                <field name="module_account_bank_statement_import_ofx" class="oe_inline"/>
+                                <label for="module_account_bank_statement_import_ofx"/>
+                            </div>
+                            <div>
+                                <field name="module_account_bank_statement_import_qif" class="oe_inline"/>
+                                <label for="module_account_bank_statement_import_qif"/>
+                            </div>
+                        </div>
                     </group>
                     <separator name="analytic_account" string="Analytic Accounting" invisible="1"/>
                     <group name="analytic_account_sale" invisible="1">
index 65fb581..75c94a6 100644 (file)
@@ -75,5 +75,7 @@
 -
   I check that the past accounts are taken into account in partner credit
 -
-  !assert {model: res.partner, id: base.res_partner_2, string: Total Receivable does not takes unreconciled moves of previous years}:
-    - credit == 1040.0
+  !python {model: res.partner}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('base.res_partner_2')).credit
+    assert float_compare(credit, 1040.0, precision_digits=2) == 0, "Total Receivable does not takes unreconciled moves of previous years"
index a59ede0..df4b99b 100644 (file)
@@ -62,6 +62,7 @@
   I verify that account move is created
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     move_obj = self.pool.get('account.move')
     inv = self.browse(cr, uid, ref('account_invoice_supplier0'))
     move = inv.move_id
@@ -73,7 +74,7 @@
     amt_compute = move_obj._amount_compute(cr, uid, list(ids), 'amount', None, {'lang': u'en_US', 'active_model': 'ir.ui.menu',
       'active_ids': [ref('menu_action_move_journal_line_form')], 'tz': False, 'active_id': ref('menu_action_move_journal_line_form')}, where ='')
     move_amount = amt_compute.values()
-    assert(inv.move_id and move.period_id.id == get_period and move_amount[0] == 3100.0), ('Journal Entries has not been created')
+    assert(inv.move_id and move.period_id.id == get_period and float_compare(move_amount[0], 3100.0, precision_digits=2) == 0), ('Journal Entries has not been created')
 -
   I cancel the account move which is in posted state and verifies that it gives warning message
 -
@@ -92,5 +93,6 @@
   I verify that 'Period Sum' and 'Year sum' of the tax code are the expected values 
 -
   !python {model: account.tax.code}: |
+    from openerp.tools import float_compare
     tax_code = self.browse(cr, uid, ref('tax_case'))
-    assert(tax_code.sum_period == 100.0 and tax_code.sum == 100.0), "Incorrect 'Period Sum' / 'Year sum' expected twice 100.0, got period=%r and year=%r)" % (tax_code.sum_period,tax_code.sum)
+    assert(float_compare(tax_code.sum_period, 100.0, precision_digits=2) == 0 and float_compare(tax_code.sum, 100.0, precision_digits=2) == 0), "Incorrect 'Period Sum' / 'Year sum' expected twice 100.0, got period=%r and year=%r)" % (tax_code.sum_period,tax_code.sum)
index 2225ae5..a0c3257 100644 (file)
@@ -49,6 +49,7 @@
 -
   !python {model: account.invoice}: |
     import time
+    from openerp.tools import float_compare
     edi_document = {
         "__id": "account:b33adf8a-decd-11f0-a4de-702a04e25700.random_invoice_763jsms",
         "__module": "account",
     for inv_line in invoice_new.invoice_line:
         if inv_line.name == 'PC Assemble SC234':
             assert inv_line.uos_id.name == "Unit" , "uom is not same"
-            assert inv_line.price_unit == 10 , "price unit is not same"
-            assert inv_line.quantity == 1 , "product qty is not same"
-            assert inv_line.price_subtotal == 10, "price sub total is not same"
+            assert float_compare(inv_line.price_unit, 10, precision_digits=2) == 0, "price unit is not same"
+            assert float_compare(inv_line.quantity, 1, precision_digits=2) == 0, "product qty is not same"
+            assert float_compare(inv_line.price_subtotal, 10, precision_digits=2) == 0, "price sub total is not same"
         elif inv_line.name == 'PC on Demand':
             assert inv_line.uos_id.name == "Unit" , "uom is not same"
-            assert inv_line.price_unit == 100 , "price unit is not same"
-            assert inv_line.quantity == 5 , "product qty is not same"
-            assert inv_line.price_subtotal == 500, "price sub total is not same"
+            assert float_compare(inv_line.price_unit, 100, precision_digits=2) == 0, "price unit is not same"
+            assert float_compare(inv_line.quantity, 5, precision_digits=2) == 0, "product qty is not same"
+            assert float_compare(inv_line.price_subtotal, 500, precision_digits=2) == 0, "price sub total is not same"
         else:
             raise AssertionError('unknown invoice line: %s' % inv_line)
     for inv_tax in invoice_new.tax_line:
 -
   !python {model: account.invoice}: |
     import time
+    from openerp.tools import float_compare
     edi_document = {
         "__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.random_invoice_763jsms", 
         "__module": "account", 
     for inv_line in invoice_new.invoice_line:
         if inv_line.name == 'Basic PC':
             assert inv_line.uos_id.name == "PCE" , "uom is not same"
-            assert inv_line.price_unit == 10 , "price unit is not same"
-            assert inv_line.quantity == 1 , "product qty is not same"
-            assert inv_line.price_subtotal == 10, "price sub total is not same"
+            assert float_compare(inv_line.price_unit, 10, precision_digits=2) == 0, "price unit is not same"
+            assert float_compare(inv_line.quantity, 1, precision_digits=2) == 0, "product qty is not same"
+            assert float_compare(inv_line.price_subtotal, 10, precision_digits=2) == 0, "price sub total is not same"
         elif inv_line.name == 'Medium PC':
             assert inv_line.uos_id.name == "PCE" , "uom is not same"
-            assert inv_line.price_unit == 100 , "price unit is not same"
-            assert inv_line.quantity == 5 , "product qty is not same"
-            assert inv_line.price_subtotal == 500, "price sub total is not same"
+            assert float_compare(inv_line.price_unit, 100, precision_digits=2) == 0, "price unit is not same"
+            assert float_compare(inv_line.quantity, 5, precision_digits=2) == 0, "product qty is not same"
+            assert float_compare(inv_line.price_subtotal, 500, precision_digits=2) == 0, "price sub total is not same"
         else:
             raise AssertionError('unknown invoice line: %s' % inv_line)
     for inv_tax in invoice_new.tax_line:
index 4f594b6..ef4eced 100644 (file)
@@ -38,8 +38,9 @@
   I test the generated invoice
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     aid = ref('account_analytic_analysis.contract_main')
     ids = self.search(cr, uid, [('invoice_line.account_analytic_id','=',aid)], context=context)
     assert len(ids)>=1, 'No invoice created for the contract'
     for invoice in self.browse(cr, uid, ids,context=context):
-        assert invoice.amount_untaxed == 150.0, "The invoice total is different than 150!"
+        assert float_compare(invoice.amount_untaxed, 150.0, precision_digits=2) == 0, "The invoice total is different than 150!"
index 32f5fd1..c26fe74 100644 (file)
 -
   I check the Stock Interim account (Received) is credited successfully.
 -
-  !assert {model: account.account, id : account_anglo_stock_input, string : Stock Interim account (Received) is not credited successfully.}:
-    - credit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_stock_input')).credit
+    float_compare(credit, 9, precision_digits=2) == 0, "Stock Interim account (Received) is not credited successfully."
 -
   I check the Stock valuation account is debited sucessfully.
 -
-  !assert {model: account.account, id : account_anglo_stock_valuation, string : Stock valuation account is not debited successfully.}:
-    - debit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_stock_valuation')).debit
+    float_compare(debit, 9, precision_digits=2) == 0, "Stock valuation account is not debited successfully."
 -
   I Validate Invoice of Purchase Order.
 -
 -
   I check the Stock Interim account (Received) is debited sucessfully when Invoice validated.
 -
-  !assert {model: account.account, id : account_anglo_stock_input, string : Stock Interim account (Received) is not debited successfully.}:
-    - debit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_stock_input')).debit
+    float_compare(debit, 9, precision_digits=2) == 0, "Stock Interim account (Received) is not debited successfully."
 -
   I check the Price difference creditor Account is debited sucessfully when Invoice validated.
 -
-  !assert {model: account.account, id : account_anglo_price_difference, string : Price difference creditor Account is not debited successfully.}:
-    - debit == 1
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_price_difference')).debit
+    float_compare(debit, 1, precision_digits=2) == 0, "Price difference creditor Account is not debited successfully."
 -
   I check Payable(creditor) Account is Credited sucessfully when Invoice validated.
 -
-  !assert {model: account.account, id : account_anglo_payable, string : Payable(creditor) Account is not Credited successfully.}:
-    - credit == 10
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_payable')).credit
+    float_compare(credit, 10, precision_digits=2) == 0, "Payable(creditor) Account is not Credited successfully."
 -
   I open the Invoice.
 -
 -
   I check Payable(Creditors) Account is Debited sucessfully after invoice paid.
 -
-  !assert {model: account.account, id : account_anglo_payable, string : Payable(Creditors) Account is not Debited successfully.}:
-    - debit == 10
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_payable')).debit
+    assert float_compare(debit, 10, precision_digits=2) == 0, "Payable(Creditors) Account is not Debited successfully."
 -
   I check Bank/Cash account is credited sucessfully after invoice paid.
 -
-  !assert {model: account.account, id : account_anglo_cash, string: Bank/Cash account is not credited successfully.}:
-    - credit == 10
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_cash')).credit
+    float_compare(credit, 10, precision_digits=2) == 0, "Bank/Cash account is not credited successfully."
 -
   I create an Outgoing Picking order
 -
 - 
   I check Stock Interim account (Delivery) is debited successfully.
 -
-  !assert {model: account.account, id : account_anglo_stock_output, string : Stock Interim account (Delivery) is not debited successfully.}:
-    - debit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_stock_output')).debit
+    float_compare(debit, 9, precision_digits=2) == 0, "Stock Interim account (Delivery) is not debited successfully."
 -
   I check the Stock valuation account is credited sucessfully.
 -
-  !assert {model: account.account, id : account_anglo_stock_valuation, string : Stock valuation account is not credited successfully.}: 
-    - credit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_stock_valuation')).credit
+    float_compare(credit, 9, precision_digits=2) == 0, "Stock valuation account is not credited successfully."
 -
   As the Invoice state of the picking order is To be invoiced. I create invoice for my outgoing picking order.
 -
 -
   I check Income Account is Credited sucessfully when Invoice validated.
 -
-  !assert {model: account.account, id : account_anglo_income, string : Income Account is not Credited successfully.}:
-    - credit == 20
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_income')).credit
+    float_compare(credit, 20, precision_digits=2) == 0, "Income Account is not Credited successfully."
 -
   I check Cost of goods sold account for debit.
 -
-  !assert {model: account.account, id : account_anglo_cogs, string : Cost of goods sale is not Debited successfully.}:
-    - debit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_cogs')).debit
+    float_compare(debit, 9, precision_digits=2) == 0, "Cost of goods sale is not Debited successfully."
 -
   I check Stock Interim account (Delivery)
 -
-  !assert {model: account.account, id : account_anglo_stock_output, string : Stock Interim account (Delivery) is not credited successfully.}:
-    - credit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_stock_output')).credit
+    float_compare(credit, 9, precision_digits=2) == 0, "Stock Interim account (Delivery) is not credited successfully."
 -
   I check Receivable(Debtor) Account for debit.
 -
-  !assert {model: account.account, id : account_anglo_receivable, string : Receivable(Debtors) Account is not Debited successfully.}: 
-    - debit == 20
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_receivable')).debit
+    float_compare(debit, 20, precision_digits=2) == 0, "Receivable(Debtors) Account is not Debited successfully."
 -
   I pay the invoice.
 -
 -
   I check Receivable(Debtor) Account for credit.
 -
-  !assert {model: account.account, id : account_anglo_receivable, string : Receivable(Debtors) Account is not Credited successfully.}: 
-    - credit == 20
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_receivable')).credit
+    float_compare(credit, 20, precision_digits=2) == 0, "Receivable(Debtors) Account is not Credited successfully."
 -
   I check Bank/Cash account is debited sucessfully after invoice paid.
 -
-  !assert {model: account.account, id : account_anglo_cash, string: Bank/Cash account is not successfully credited.}:
-    - debit == 20
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_cash')).debit
+    float_compare(debit, 20, precision_digits=2) == 0, "Bank/Cash account is not successfully credited."
index daeefc0..309abec 100644 (file)
 -
   I check the Stock Interim account (Received) is credit successfully.
 -
-  !assert {model: account.account, id : account_anglo_stock_input_fifo, string : Stock Interim account (Received) is not credited successfully.}:
-    - credit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_stock_input_fifo')).credit
+    assert float_compare(credit, 9, precision_digits=2) == 0, "Stock Interim account (Received) is not credited successfully."
 -
   I check the Stock valuation account is debit sucessfully.
 -
-  !assert {model: account.account, id : account_anglo_stock_valuation_fifo, string : Stock valuation account is not debited successfully.}:
-    - debit == 9
+  !python {model: account.account, id}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_stock_valuation_fifo')).debit
+    assert float_compare(debit, 9, precision_digits=2) == 0, "Stock valuation account is not debited successfully."
 -
   I Validate Invoice of Purchase Order after having changed the price to 10.
 -
 -
   I check the Stock Interim account (Received) is debited sucessfully when Invoice validated.
 -
-  !assert {model: account.account, id : account_anglo_stock_input_fifo, string : Stock Interim account (Received) is not debited successfully.}:
-    - debit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_stock_input_fifo')).debit
+    assert float_compare(debit, 9, precision_digits=2) == 0, "Stock Interim account (Received) is not debited successfully."
 -
   I check the Price difference creditor Account is debited sucessfully when Invoice validated.
 -
-  !assert {model: account.account, id : account_anglo_price_difference_fifo, string : Price difference creditor Account is not debited successfully.}:
-    - debit == 1
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_price_difference_fifo')).debit
+    assert float_compare(debit, 1, precision_digits=2) == 0, "Price difference creditor Account is not debited successfully."
 -
   I check Payable(creditor) Account is Credited sucessfully when Invoice validated.
 -
-  !assert {model: account.account, id : account_anglo_payable_fifo, string : Payable(creditor) Account is not Credited successfully.}:
-    - credit == 10
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_payable_fifo')).credit
+    assert float_compare(credit, 10, precision_digits=2) == 0, "Payable(creditor) Account is not Credited successfully."
 -
   I pay the invoice.
 -
 -
   I check Payable(Creditors) Account is Debited sucessfully after invoice paid.
 -
-  !assert {model: account.account, id : account_anglo_payable_fifo, string : Payable(Creditors) Account is not Debited successfully.}:
-    - debit == 10
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_payable_fifo')).debit
+    assert float_compare(debit, 10, precision_digits=2) == 0, "Payable(Creditors) Account is not Debited successfully."
 -
   I check Bank/Cash account is credited sucessfully after invoice paid.
 -
-  !assert {model: account.account, id : account_anglo_cash_fifo, string: Bank/Cash account is not credited successfully.}:
-    - credit == 10
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_cash_fifo')).credit
+    assert float_compare(credit, 10, precision_digits=2) == 0, "Bank/Cash account is not credited successfully."
 -
   I create an Outgoing Picking order
 -
 - 
   I check Stock Interim account (Delivery) is debited successfully.
 -
-  !assert {model: account.account, id : account_anglo_stock_output_fifo, string : Stock Interim account (Delivery) is not debited successfully.}:
-    - debit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_stock_output_fifo')).debit
+    assert float_compare(debit, 9, precision_digits=2) == 0, "Stock Interim account (Delivery) is not debited successfully."
 -
   I check the Stock valuation account is credited sucessfully.
 -
-  !assert {model: account.account, id : account_anglo_stock_valuation_fifo, string : Stock valuation account is not credited successfully.}: 
-    - credit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_stock_valuation_fifo')).credit
+    assert float_compare(credit, 9, precision_digits=2) == 0, "Stock valuation account is not credited successfully."
 -
   As the Invoice state of the picking order is To be invoiced. I create invoice for my outgoing picking order.
 -
 -
   I check Income Account is Credited sucessfully when Invoice validated.
 -
-  !assert {model: account.account, id : account_anglo_income_fifo, string : Income Account is not Credited successfully.}:
-    - credit == 20
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_income_fifo')).credit
+    assert float_compare(credit, 20, precision_digits=2) == 0, "Income Account is not Credited successfully."
 -
   I check Cost of goods sold account for debit.
 -
-  !assert {model: account.account, id : account_anglo_cogs_fifo, string : Cost of goods sale is not Debited successfully.}:
-    - debit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_cogs_fifo')).debit
+    assert float_compare(debit, 9, precision_digits=2) == 0, "Cost of goods sale is not Debited successfully."
 -
   I check Stock Interim account (Delivery)
 -
-  !assert {model: account.account, id : account_anglo_stock_output_fifo, string : Stock Interim account (Delivery) is not credited successfully.}:
-    - credit == 9
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_stock_output_fifo')).credit
+    assert float_compare(credit, 9, precision_digits=2) == 0, "Stock Interim account (Delivery) is not credited successfully."
 -
   I check Receivable(Debtor) Account for debit.
 -
-  !assert {model: account.account, id : account_anglo_receivable_fifo, string : Receivable(Debtors) Account is not Debited successfully.}: 
-    - debit == 20
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_receivable_fifo')).debit
+    assert float_compare(debit, 20, precision_digits=2) == 0, "Receivable(Debtors) Account is not Debited successfully."
 -
   I pay the invoice.
 -
 -
   I check Receivable(Debtor) Account for credit.
 -
-  !assert {model: account.account, id : account_anglo_receivable_fifo, string : Receivable(Debtors) Account is not Credited successfully.}: 
-    - credit == 20
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    credit = self.browse(cr, uid, ref('account_anglo_receivable_fifo')).credit
+    assert float_compare(credit, 20, precision_digits=2) == 0, "Receivable(Debtors) Account is not Credited successfully."
 -
   I check Bank/Cash account is debited sucessfully after invoice paid.
 -
-  !assert {model: account.account, id : account_anglo_cash_fifo, string: Bank/Cash account is not successfully credited.}:
-    - debit == 20
+  !python {model: account.account}: |
+    from openerp.tools import float_compare
+    debit = self.browse(cr, uid, ref('account_anglo_cash_fifo')).debit
+    assert float_compare(debit, 20, precision_digits=2) == 0, "Bank/Cash account is not successfully credited."
diff --git a/addons/account_bank_statement_import/__init__.py b/addons/account_bank_statement_import/__init__.py
new file mode 100644 (file)
index 0000000..edce853
--- /dev/null
@@ -0,0 +1,5 @@
+# -*- encoding: utf-8 -*-
+
+import account_bank_statement_import
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_bank_statement_import/__openerp__.py b/addons/account_bank_statement_import/__openerp__.py
new file mode 100644 (file)
index 0000000..6ac599c
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- encoding: utf-8 -*-
+{
+    'name': 'Account Bank Statement Import',
+    'version': '1.0',
+    'author': 'OpenERP SA',
+    'depends': ['account'],
+    'demo': [],
+    'description' : """Generic Wizard to Import Bank Statements. Includes the import of files in .OFX format""",
+    'data' : [
+        'account_bank_statement_import_view.xml',
+    ],
+    'demo': [
+        'demo/fiscalyear_period.xml',
+        'demo/partner_bank.xml',
+    ],
+    'auto_install': False,
+    'installable': True,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_bank_statement_import/account_bank_statement_import.py b/addons/account_bank_statement_import/account_bank_statement_import.py
new file mode 100644 (file)
index 0000000..7866aeb
--- /dev/null
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+
+import logging
+_logger = logging.getLogger(__name__)
+
+_IMPORT_FILE_TYPE = [('none', _('No Import Format Available'))]
+
+def add_file_type(selection_value):
+    global _IMPORT_FILE_TYPE
+    if _IMPORT_FILE_TYPE[0][0] == 'none':
+        _IMPORT_FILE_TYPE = [selection_value]
+    else:
+        _IMPORT_FILE_TYPE.append(selection_value)
+
+class account_bank_statement_import(osv.TransientModel):
+    _name = 'account.bank.statement.import'
+    _description = 'Import Bank Statement'
+
+    def _get_import_file_type(self, cr, uid, context=None):
+        return _IMPORT_FILE_TYPE
+
+    _columns = {
+        'data_file': fields.binary('Bank Statement File', required=True, help='Get you bank statements in electronic format from your bank and select them here.'),
+        'file_type': fields.selection(_get_import_file_type, 'File Type', required=True),
+        'journal_id': fields.many2one('account.journal', 'Journal', required=True, help="The journal for which the bank statements will be created"),
+    }
+
+    def _get_first_file_type(self, cr, uid, context=None):
+        return self._get_import_file_type(cr, uid, context=context)[0][0]
+
+    def _get_default_journal(self, cr, uid, context=None):
+        company_id = self.pool.get('res.company')._company_default_get(cr, uid, 'account.bank.statement', context=context)
+        journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'bank'), ('company_id', '=', company_id)], context=context)
+        return journal_ids and journal_ids[0] or False
+
+    _defaults = {
+        'file_type': _get_first_file_type,
+        'journal_id': _get_default_journal,
+    }
+
+    def _detect_partner(self, cr, uid, identifying_string, identifying_field='acc_number', context=None):
+        """Try to find a bank account and its related partner for the given 'identifying_string', looking on the field 'identifying_field'.
+
+           :param identifying_string: varchar
+           :param identifying_field: varchar corresponding to the name of a field of res.partner.bank
+           :returns: tuple(ID of the bank account found or False, ID of the partner for the bank account found or False)
+        """
+        partner_id = False
+        bank_account_id = False
+        if identifying_string:
+            ids = self.pool.get('res.partner.bank').search(cr, uid, [(identifying_field, '=', identifying_string)], context=context)
+            if ids:
+                bank_account_id = ids[0]
+                partner_id = self.pool.get('res.partner.bank').browse(cr, uid, bank_account_id, context=context).partner_id.id
+            else:
+                #create the bank account, not linked to any partner. The reconciliation will link the partner manually
+                #chosen at the bank statement final confirmation time.
+                try:
+                    type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal')
+                    type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context)
+                    bank_code = type_id.code
+                except ValueError:
+                    bank_code = 'bank'
+                acc_number = identifying_field == 'acc_number' and identifying_string or _('Undefined')
+                bank_account_vals = {
+                    'acc_number': acc_number,
+                    'state': bank_code,
+                }
+                bank_account_vals[identifying_field] = identifying_string
+                bank_account_id = self.pool.get('res.partner.bank').create(cr, uid, bank_account_vals, context=context)
+        return bank_account_id, partner_id
+
+    def import_bank_statement(self, cr, uid, bank_statement_vals=False, context=None):
+        """ Get a list of values to pass to the create() of account.bank.statement object, and returns a list of ID created using those values"""
+        statement_ids = []
+        for vals in bank_statement_vals:
+            statement_ids.append(self.pool.get('account.bank.statement').create(cr, uid, vals, context=context))
+        return statement_ids
+
+    def process_none(self, cr, uid, data_file, journal_id=False, context=None):
+        raise osv.except_osv(_('Error'), _('No available format for importing bank statement. You can install one of the file format available through the module installation.'))
+
+    def parse_file(self, cr, uid, ids, context=None):
+        """ Process the file chosen in the wizard and returns a list view of the imported bank statements"""
+        data = self.browse(cr, uid, ids[0], context=context)
+        vals = getattr(self, "process_%s" % data.file_type)(cr, uid, data.data_file, data.journal_id.id, context=context)
+        statement_ids = self.import_bank_statement(cr, uid, vals, context=context)
+        model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'action_bank_statement_tree')
+        action = self.pool[model].read(cr, uid, action_id, context=context)
+        action['domain'] = "[('id', 'in', [" + ', '.join(map(str, statement_ids)) + "])]"
+        return action
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_bank_statement_import/account_bank_statement_import_view.xml b/addons/account_bank_statement_import/account_bank_statement_import_view.xml
new file mode 100644 (file)
index 0000000..b637a07
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" ?>
+<openerp>
+    <data>
+
+        <record id="account_bank_statement_import_view" model="ir.ui.view">
+            <field name="name">Import Bank Statements</field>
+            <field name="model">account.bank.statement.import</field>
+            <field name="priority">1</field>
+            <field name="arch" type="xml">
+                <form string="Import Bank Statements" version="7.0">
+                    <group>
+                        <group>
+                            <field name="data_file"/>
+                            <field name="file_type"/>
+                            <field name="journal_id" domain="[('type', '=', 'bank')]" context="{'default_type':'bank'}"/>
+                        </group>
+                        <group>
+                            <b colspan="2"> How to import your bank statement in OpenERP.</b>
+                            <label string= "1. Go to your bank account website." colspan="2"/>
+                            <label string= "2. Download your bank statements in the right format. (.OFX, .QIF or CODA are accepted)" colspan="2"/>
+                            <label string= "3. Upload right here the bank statements file into OpenERP. Click Import." colspan="2"/>
+                        </group>
+                    </group>
+                    <footer>
+                        <button name="parse_file" string="_Import" type="object" class="oe_highlight"/>
+                        or
+                        <button string="Cancel" class="oe_link" special="cancel"/>
+                    </footer>
+                </form>
+            </field>
+        </record>
+
+        <record id="action_account_bank_statement_import" model="ir.actions.act_window">
+            <field name="name">Import Bank Statements</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">account.bank.statement.import</field>
+            <field name="view_type">form</field>
+            <field name="view_mode">form</field>
+            <field name="target">new</field>
+            <field name="view_id" ref="account_bank_statement_import_view"/>
+        </record>
+
+        <menuitem parent="account.menu_finance_bank_and_cash" id="menu_account_bank_statement_import" action="action_account_bank_statement_import" sequence="11"/>
+
+    </data>
+</openerp>
diff --git a/addons/account_bank_statement_import/demo/fiscalyear_period.xml b/addons/account_bank_statement_import/demo/fiscalyear_period.xml
new file mode 100644 (file)
index 0000000..41b4be3
--- /dev/null
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+
+        <!--
+        Fiscal year
+        -->    
+        
+        <record id="data_fiscalyear_2013" model="account.fiscalyear">
+            <field eval="'Fiscal Year X 2013'" name="name"/>
+            <field eval="'FY2013'" name="code"/>
+            <field eval="'2013-01-01'" name="date_start"/>
+            <field eval="'2013-12-31'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+
+        <!--
+        Fiscal Periods 2013
+        -->    
+        
+        <record id="period_1_2013" model="account.period">
+            <field eval="'01/2013'" name="code"/>
+            <field eval="'X 01/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-01-01'" name="date_start"/>
+            <field eval="'2013-01-31'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+
+        <record id="period_2_2013" model="account.period">
+            <field eval="'02/2013'" name="code"/>
+            <field eval="'X 02/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-02-01'" name="date_start"/>
+            <field eval="'2013-02-28'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+        <record id="period_3_2013" model="account.period">
+            <field eval="'03/2013'" name="code"/>
+            <field eval="'X 03/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-03-01'" name="date_start"/>
+            <field eval="'2013-03-31'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+        <record id="period_4_2013" model="account.period">
+            <field eval="'04/2013'" name="code"/>
+            <field eval="'X 04/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-04-01'" name="date_start"/>
+            <field eval="'2013-04-30'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+        <record id="period_5_2013" model="account.period">
+            <field eval="'05/2013'" name="code"/>
+            <field eval="'X 05/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-05-01'" name="date_start"/>
+            <field eval="'2013-05-31'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+        <record id="period_6_2013" model="account.period">
+            <field eval="'06/2013'" name="code"/>
+            <field eval="'X 06/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-06-01'" name="date_start"/>
+            <field eval="'2013-06-30'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+        <record id="period_7_2013" model="account.period">
+            <field eval="'07/2013'" name="code"/>
+            <field eval="'X 07/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-07-01'" name="date_start"/>
+            <field eval="'2013-07-31'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+        <record id="period_8_2013" model="account.period">
+            <field eval="'08/2013'" name="code"/>
+            <field eval="'X 08/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-08-01'" name="date_start"/>
+            <field eval="'2013-08-31'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+        <record id="period_9_2013" model="account.period">
+            <field eval="'09/2013'" name="code"/>
+            <field eval="'X 09/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-09-01'" name="date_start"/>
+            <field eval="'2013-09-30'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+        <record id="period_10_2013" model="account.period">
+            <field eval="'10/2013'" name="code"/>
+            <field eval="'X 10/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-10-01'" name="date_start"/>
+            <field eval="'2013-10-31'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+        <record id="period_11_2013" model="account.period">
+            <field eval="'11/2013'" name="code"/>
+            <field eval="'X 11/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-11-01'" name="date_start"/>
+            <field eval="'2013-11-30'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+        <record id="period_12_2013" model="account.period">
+            <field eval="'12/2013'" name="code"/>
+            <field eval="'X 12/2013'" name="name"/>
+            <field name="fiscalyear_id" ref="data_fiscalyear_2013"/>
+            <field eval="'2013-12-01'" name="date_start"/>
+            <field eval="'2013-12-31'" name="date_stop"/>
+            <field name="company_id" ref="base.main_company"/>
+        </record>
+    </data>
+</openerp>
diff --git a/addons/account_bank_statement_import/demo/partner_bank.xml b/addons/account_bank_statement_import/demo/partner_bank.xml
new file mode 100644 (file)
index 0000000..47e8fa7
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+
+        <record id="ofx_partner_bank_1" model="res.partner.bank">
+            <field name="owner_name">Agrolait</field>
+            <field name="acc_number">00987654321</field>
+            <field name="partner_id" ref="base.res_partner_2"></field>
+            <field name="state">bank</field>
+            <field name="bank" ref="base.res_bank_1"/>
+        </record>
+
+        <record id="ofx_partner_bank_2" model="res.partner.bank">
+            <field name="owner_name">China Export</field>
+            <field name="acc_number">00987654322</field>
+            <field name="partner_id" ref="base.res_partner_3"></field>
+            <field name="state">bank</field>
+            <field name="bank" ref="base.res_bank_1"/>
+        </record>
+
+        <record id="qif_partner_bank_1" model="res.partner.bank">
+            <field name="owner_name">Delta PC</field>
+            <field name="acc_number">10987654320</field>
+            <field name="partner_id" ref="base.res_partner_4"></field>
+            <field name="state">bank</field>
+            <field name="bank" ref="base.res_bank_1"/>
+        </record>
+
+        <record id="qif_partner_bank_2" model="res.partner.bank">
+            <field name="owner_name">Epic Technologies</field>
+            <field name="acc_number">10987654322</field>
+            <field name="partner_id" ref="base.res_partner_5"></field>
+            <field name="state">bank</field>
+            <field name="bank" ref="base.res_bank_1"/>
+        </record>
+
+    </data>
+</openerp>
diff --git a/addons/account_bank_statement_import_ofx/__init__.py b/addons/account_bank_statement_import_ofx/__init__.py
new file mode 100644 (file)
index 0000000..fe838c2
--- /dev/null
@@ -0,0 +1,5 @@
+# -*- encoding: utf-8 -*-
+
+import account_bank_statement_import_ofx
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_bank_statement_import_ofx/__openerp__.py b/addons/account_bank_statement_import_ofx/__openerp__.py
new file mode 100644 (file)
index 0000000..baf0c66
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- encoding: utf-8 -*-
+{
+    'name': 'Import OFX Bank Statement',
+    'version': '1.0',
+    'author': 'OpenERP SA',
+    'depends': ['account_bank_statement_import'],
+    'demo': [],
+    'description' : """
+Module to import OFX bank statements.
+======================================
+
+This module allows you to import the machine readable OFX Files in Odoo: they are parsed and stored in human readable format in 
+Accounting \ Bank and Cash \ Bank Statements.
+
+Bank Statements may be generated containing a subset of the OFX information (only those transaction lines that are required for the 
+creation of the Financial Accounting records). 
+    
+    """,
+    'data' : [],
+    'demo': [],
+    'auto_install': False,
+    'installable': True,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_bank_statement_import_ofx/account_bank_statement_import_ofx.py b/addons/account_bank_statement_import_ofx/account_bank_statement_import_ofx.py
new file mode 100644 (file)
index 0000000..13966c0
--- /dev/null
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+import logging
+import base64
+import os
+
+from openerp.osv import osv
+from openerp.tools.translate import _
+
+_logger = logging.getLogger(__name__)
+
+from openerp.addons.account_bank_statement_import import account_bank_statement_import as ibs
+ibs.add_file_type(('ofx', 'OFX'))
+
+try:
+    from ofxparse import OfxParser as ofxparser
+except ImportError:
+    _logger.warning("OFX parser unavailable because the `ofxparse` Python library cannot be found."
+                    "It can be downloaded and installed from `https://pypi.python.org/pypi/ofxparse`.")
+    ofxparser = None
+
+class account_bank_statement_import(osv.TransientModel):
+    _inherit = 'account.bank.statement.import'
+
+    def process_ofx(self, cr, uid, data_file, journal_id=False, context=None):
+        """ Import a file in the .OFX format"""
+        if ofxparser is None:
+            raise osv.except_osv(_("Error"), _("OFX parser unavailable because the `ofxparse` Python library cannot be found."
+                    "It can be downloaded and installed from `https://pypi.python.org/pypi/ofxparse`."))
+        try:
+            tempfile = open("temp.ofx", "w+")
+            tempfile.write(base64.decodestring(data_file))
+            tempfile.read()
+            pathname = os.path.dirname('temp.ofx')
+            path = os.path.join(os.path.abspath(pathname), 'temp.ofx')
+            ofx = ofxparser.parse(file(path))
+        except:
+            raise osv.except_osv(_('Import Error!'), _('Please check OFX file format is proper or not.'))
+        line_ids = []
+        total_amt = 0.00
+        try:
+            for transaction in ofx.account.statement.transactions:
+                bank_account_id, partner_id = self._detect_partner(cr, uid, transaction.payee, identifying_field='owner_name', context=context)
+                vals_line = {
+                    'date': transaction.date,
+                    'name': transaction.payee + ': ' + transaction.memo,
+                    'ref': transaction.id,
+                    'amount': transaction.amount,
+                    'partner_id': partner_id,
+                    'bank_account_id': bank_account_id,
+                }
+                total_amt += float(transaction.amount)
+                line_ids.append((0, 0, vals_line))
+        except Exception, e:
+            raise osv.except_osv(_('Error!'), _("Following problem has been occurred while importing your file, Please verify the file is proper or not.\n\n %s" % e.message))
+        st_start_date = ofx.account.statement.start_date or False
+        st_end_date = ofx.account.statement.end_date or False
+        period_obj = self.pool.get('account.period')
+        if st_end_date:
+            period_ids = period_obj.find(cr, uid, st_end_date, context=context)
+        else:
+            period_ids = period_obj.find(cr, uid, st_start_date, context=context)
+        vals_bank_statement = {
+            'name': ofx.account.routing_number,
+            'balance_start': ofx.account.statement.balance,
+            'balance_end_real': float(ofx.account.statement.balance) + total_amt,
+            'period_id': period_ids and period_ids[0] or False,
+            'journal_id': journal_id
+        }
+        vals_bank_statement.update({'line_ids': line_ids})
+        os.remove(path)
+        return [vals_bank_statement]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_bank_statement_import_ofx/test_ofx_file/test_ofx.ofx b/addons/account_bank_statement_import_ofx/test_ofx_file/test_ofx.ofx
new file mode 100644 (file)
index 0000000..37df4d0
--- /dev/null
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="ASCII"?>
+<?OFX OFXHEADER="200" VERSION="211" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>
+<OFX>
+  <SIGNONMSGSRSV1>
+    <SONRS>
+      <STATUS>
+        <CODE>0</CODE>
+        <SEVERITY>INFO</SEVERITY>
+      </STATUS>
+      <DTSERVER>20130831165153.000[-8:PST]</DTSERVER>
+      <LANGUAGE>ENG</LANGUAGE>
+    </SONRS>
+  </SIGNONMSGSRSV1>
+  <BANKMSGSRSV1>
+    <STMTTRNRS>
+      <TRNUID>0</TRNUID>
+      <STATUS>
+        <CODE>0</CODE>
+        <SEVERITY>INFO</SEVERITY>
+      </STATUS>
+      <STMTRS>
+        <CURDEF>USD</CURDEF>
+        <BANKACCTFROM>
+          <BANKID>000000123</BANKID>
+          <ACCTID>123456</ACCTID>
+          <ACCTTYPE>CHECKING</ACCTTYPE>
+        </BANKACCTFROM>
+        <BANKTRANLIST>
+          <DTSTART>20130801</DTSTART>
+          <DTEND>20130831165153.000[-8:PST]</DTEND>
+          <STMTTRN>
+            <TRNTYPE>POS</TRNTYPE>
+            <DTPOSTED>20130824080000</DTPOSTED>
+            <TRNAMT>-80</TRNAMT>
+            <FITID>219378</FITID>
+            <NAME>Agrolait</NAME>
+          </STMTTRN>
+        </BANKTRANLIST>
+        <BANKTRANLIST>
+          <DTSTART>20130801</DTSTART>
+          <DTEND>20130831165153.000[-8:PST]</DTEND>
+          <STMTTRN>
+            <TRNTYPE>POS</TRNTYPE>
+            <DTPOSTED>20130824080000</DTPOSTED>
+            <TRNAMT>-90</TRNAMT>
+            <FITID>219379</FITID>
+            <NAME>China Export</NAME>
+          </STMTTRN>
+        </BANKTRANLIST>
+        <BANKTRANLIST>
+          <DTSTART>20130801</DTSTART>
+          <DTEND>20130831165153.000[-8:PST]</DTEND>
+          <STMTTRN>
+            <TRNTYPE>POS</TRNTYPE>
+            <DTPOSTED>20130824080000</DTPOSTED>
+            <TRNAMT>-100</TRNAMT>
+            <FITID>219380</FITID>
+            <NAME>Axelor Scuba</NAME>
+          </STMTTRN>
+        </BANKTRANLIST>
+        <BANKTRANLIST>
+          <DTSTART>20130801</DTSTART>
+          <DTEND>20130831165153.000[-8:PST]</DTEND>
+          <STMTTRN>
+            <TRNTYPE>POS</TRNTYPE>
+            <DTPOSTED>20130824080000</DTPOSTED>
+            <TRNAMT>-90</TRNAMT>
+            <FITID>219381</FITID>
+            <NAME>China Scuba</NAME>
+          </STMTTRN>
+        </BANKTRANLIST>
+        <LEDGERBAL>
+          <BALAMT>2156.56</BALAMT>
+          <DTASOF>20130831165153</DTASOF>
+        </LEDGERBAL>
+      </STMTRS>
+    </STMTTRNRS>
+  </BANKMSGSRSV1>
+  <CREDITCARDMSGSRSV1>
+    <CCSTMTTRNRS>
+      <TRNUID>0</TRNUID>
+      <STATUS>
+        <CODE>0</CODE>
+        <SEVERITY>INFO</SEVERITY>
+      </STATUS>
+      <CCSTMTRS>
+        <CURDEF>USD</CURDEF>
+        <CCACCTFROM>
+          <ACCTID>123412341234</ACCTID>
+        </CCACCTFROM>
+        <BANKTRANLIST>
+        </BANKTRANLIST>
+        <LEDGERBAL>
+          <BALAMT>-562.00</BALAMT>
+          <DTASOF>20130831165153</DTASOF>
+        </LEDGERBAL>
+      </CCSTMTRS>
+    </CCSTMTTRNRS>
+  </CREDITCARDMSGSRSV1>
+</OFX>
diff --git a/addons/account_bank_statement_import_ofx/tests/__init__.py b/addons/account_bank_statement_import_ofx/tests/__init__.py
new file mode 100644 (file)
index 0000000..8a5a8e9
--- /dev/null
@@ -0,0 +1,5 @@
+from . import test_import_bank_statement
+
+checks = [
+        test_import_bank_statement
+]
diff --git a/addons/account_bank_statement_import_ofx/tests/test_import_bank_statement.py b/addons/account_bank_statement_import_ofx/tests/test_import_bank_statement.py
new file mode 100644 (file)
index 0000000..967d647
--- /dev/null
@@ -0,0 +1,30 @@
+from openerp.tests.common import TransactionCase
+from openerp.modules.module import get_module_resource
+
+class TestOfxFile(TransactionCase):
+    """Tests for import bank statement ofx file format (account.bank.statement.import)
+    """
+
+    def setUp(self):
+        super(TestOfxFile, self).setUp()
+        self.statement_import_model = self.registry('account.bank.statement.import')
+        self.bank_statement_model = self.registry('account.bank.statement')
+
+    def test_ofx_file_import(self):
+        try:
+            from ofxparse import OfxParser as ofxparser
+        except ImportError:
+            #the Python library isn't installed on the server, the OFX import is unavailable and the test cannot be run
+            return True
+        cr, uid = self.cr, self.uid
+        ofx_file_path = get_module_resource('account_bank_statement_import_ofx', 'test_ofx_file', 'test_ofx.ofx')
+        ofx_file = open(ofx_file_path, 'rb').read().encode('base64')
+        bank_statement_id = self.statement_import_model.create(cr, uid, dict(
+                            file_type='ofx',
+                            data_file=ofx_file,
+                            ))
+        self.statement_import_model.parse_file(cr, uid, [bank_statement_id])
+        statement_id = self.bank_statement_model.search(cr, uid, [('name', '=', '000000123')])[0]
+        bank_st_record = self.bank_statement_model.browse(cr, uid, statement_id)
+        self.assertEquals(bank_st_record.balance_start, 2156.56)
+        self.assertEquals(bank_st_record.balance_end_real, 1796.56)
diff --git a/addons/account_bank_statement_import_qif/__init__.py b/addons/account_bank_statement_import_qif/__init__.py
new file mode 100644 (file)
index 0000000..befbd0e
--- /dev/null
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+import account_bank_statement_import_qif
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_bank_statement_import_qif/__openerp__.py b/addons/account_bank_statement_import_qif/__openerp__.py
new file mode 100644 (file)
index 0000000..dad736a
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+{
+    'name': 'Import QIF Bank Statement',
+    'version': '1.0',
+    'author': 'OpenERP SA',
+    'description': '''
+Module to import QIF bank statements.
+======================================
+
+This module allows you to import the machine readable QIF Files in Odoo: they are parsed and stored in human readable format in 
+Accounting \ Bank and Cash \ Bank Statements.
+
+Bank Statements may be generated containing a subset of the QIF information (only those transaction lines that are required for the 
+creation of the Financial Accounting records). 
+''',
+    'images' : [],
+    'depends': ['account_bank_statement_import'],
+    'demo': [],
+    'data': [],
+    'auto_install': False,
+    'installable': True,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_bank_statement_import_qif/account_bank_statement_import_qif.py b/addons/account_bank_statement_import_qif/account_bank_statement_import_qif.py
new file mode 100644 (file)
index 0000000..a253cb6
--- /dev/null
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import dateutil.parser
+import base64
+from tempfile import TemporaryFile
+
+from openerp.tools.translate import _
+from openerp.osv import osv
+
+from openerp.addons.account_bank_statement_import import account_bank_statement_import as ibs
+
+ibs.add_file_type(('qif', 'QIF'))
+
+class account_bank_statement_import(osv.TransientModel):
+    _inherit = "account.bank.statement.import"
+
+    def process_qif(self, cr, uid, data_file, journal_id=False, context=None):
+        """ Import a file in the .QIF format"""
+        try:
+            fileobj = TemporaryFile('wb+')
+            fileobj.write(base64.b64decode(data_file))
+            fileobj.seek(0)
+            file_data = ""
+            for line in fileobj.readlines():
+                file_data += line
+            fileobj.close()
+            if '\r' in file_data:
+                data_list = file_data.split('\r')
+            else:
+                data_list = file_data.split('\n')
+            header = data_list[0].strip()
+            header = header.split(":")[1]
+        except:
+            raise osv.except_osv(_('Import Error!'), _('Please check QIF file format is proper or not.'))
+        line_ids = []
+        vals_line = {}
+        total = 0
+        if header == "Bank":
+            vals_bank_statement = {}
+            for line in data_list:
+                line = line.strip()
+                if not line:
+                    continue
+                if line[0] == 'D':  # date of transaction
+                    vals_line['date'] = dateutil.parser.parse(line[1:], fuzzy=True).date()
+                    if vals_line.get('date') and not vals_bank_statement.get('period_id'):
+                        period_ids = self.pool.get('account.period').find(cr, uid, vals_line['date'], context=context)
+                        vals_bank_statement.update({'period_id': period_ids and period_ids[0] or False})
+                elif line[0] == 'T':  # Total amount
+                    total += float(line[1:].replace(',', ''))
+                    vals_line['amount'] = float(line[1:].replace(',', ''))
+                elif line[0] == 'N':  # Check number
+                    vals_line['ref'] = line[1:]
+                elif line[0] == 'P':  # Payee
+                    bank_account_id, partner_id = self._detect_partner(cr, uid, line[1:], identifying_field='owner_name', context=context)
+                    vals_line['partner_id'] = partner_id
+                    vals_line['bank_account_id'] = bank_account_id
+                    vals_line['name'] = 'name' in vals_line and line[1:] + ': ' + vals_line['name'] or line[1:]
+                elif line[0] == 'M':  # Memo
+                    vals_line['name'] = 'name' in vals_line and vals_line['name'] + ': ' + line[1:] or line[1:]
+                elif line[0] == '^':  # end of item
+                    line_ids.append((0, 0, vals_line))
+                    vals_line = {}
+                elif line[0] == '\n':
+                    line_ids = []
+                else:
+                    pass
+        else:
+            raise osv.except_osv(_('Error!'), _('Cannot support this Format !Type:%s.') % (header,))
+        vals_bank_statement.update({'balance_end_real': total,
+                                    'line_ids': line_ids,
+                                    'journal_id': journal_id})
+        return [vals_bank_statement]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_bank_statement_import_qif/test_qif_file/test_qif.qif b/addons/account_bank_statement_import_qif/test_qif_file/test_qif.qif
new file mode 100644 (file)
index 0000000..5388e6d
--- /dev/null
@@ -0,0 +1,21 @@
+!Type:Bank
+D8/12/13
+T-1,000.00
+PDelta PC
+^
+D8/15/13
+T-75.46
+PWalts Drugs
+^
+D3/3/13
+T-379.00
+PEpic Technologies
+^
+D3/4/13
+T-20.28
+PYOUR LOCAL SUPERMARKET
+^
+D3/3/13
+T-421.35
+PSPRINGFIELD WATER UTILITY
+^
diff --git a/addons/account_bank_statement_import_qif/tests/__init__.py b/addons/account_bank_statement_import_qif/tests/__init__.py
new file mode 100644 (file)
index 0000000..05b23c4
--- /dev/null
@@ -0,0 +1,5 @@
+from . import test_import_bank_statement
+checks = [
+    test_import_bank_statement
+]
+
diff --git a/addons/account_bank_statement_import_qif/tests/test_import_bank_statement.py b/addons/account_bank_statement_import_qif/tests/test_import_bank_statement.py
new file mode 100644 (file)
index 0000000..0f9ef4d
--- /dev/null
@@ -0,0 +1,27 @@
+from openerp.tests.common import TransactionCase
+from openerp.modules.module import get_module_resource
+
+class TestQifFile(TransactionCase):
+    """Tests for import bank statement qif file format (account.bank.statement.import)
+    """
+
+    def setUp(self):
+        super(TestQifFile, self).setUp()
+        self.statement_import_model = self.registry('account.bank.statement.import')
+        self.bank_statement_model = self.registry('account.bank.statement')
+        self.bank_statement_line_model = self.registry('account.bank.statement.line')
+
+    def test_qif_file_import(self):
+        from openerp.tools import float_compare
+        cr, uid = self.cr, self.uid
+        qif_file_path = get_module_resource('account_bank_statement_import_qif', 'test_qif_file', 'test_qif.qif')
+        qif_file = open(qif_file_path, 'rb').read().encode('base64')
+        bank_statement_id = self.statement_import_model.create(cr, uid, dict(
+                            file_type='qif',
+                            data_file=qif_file,
+                            ))
+        self.statement_import_model.parse_file(cr, uid, [bank_statement_id])
+        line_id = self.bank_statement_line_model.search(cr, uid, [('name', '=', 'YOUR LOCAL SUPERMARKET')])[0]
+        statement_id = self.bank_statement_line_model.browse(cr, uid, line_id).statement_id.id
+        bank_st_record = self.bank_statement_model.browse(cr, uid, statement_id)
+        assert float_compare(bank_st_record.balance_end_real, -1896.09, 2) == 0
index 84ae118..4c19c33 100644 (file)
   I check that first invoice move is correct for debtor account (debit - credit == 150.0)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_jan"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == 150.0), "Invoice move is not correct for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 150.0, precision_digits=2) == 0), "Invoice move is not correct for debtors account"
 -
   I create the second invoice on 1st February for 100 USD
 -
   I check that second invoice move is correct for debtor account (debit - credit == 80)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_feb"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == 80), "Invoice move is not correct for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 80, precision_digits=2) == 0), "Invoice move is not correct for debtors account"
 
 -
   I set the context that will be used for the encoding of all the vouchers of this file
   I fill amounts 180 for the invoice of 200$ and 70 for the invoice of 100$>
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_1_case1'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 200.00:
+        if float_compare(item.amount_unreconciled, 200.00, precision_digits=2) == 0:
             data += [(item.id, 180.0)]
         else:
             data += [(item.id, 70.0)]
   I check that writeoff amount computed is -10.0
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 1 USD/USD'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (voucher_id.writeoff_amount == -10.0), "Writeoff amount is not -10.0"
+    assert (float_compare(voucher_id.writeoff_amount, -10.0, precision_digits=2) == 0), "Writeoff amount is not -10.0"
 -
   I confirm the voucher
 -
   I check that my write-off is correct. 9 debit and 10 amount_currency
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 1 USD/USD'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.amount_currency == -180.00:
-            assert move_line.credit == 162.00, "Debtor account has wrong entry."
-        elif move_line.amount_currency == -70.00:
-            assert move_line.credit == 63.00, "Debtor account has wrong entry."
-        elif move_line.amount_currency == 10.00:
-            assert move_line.debit == 9.00, "Writeoff amount is wrong."
-        elif move_line.amount_currency == 240.00:
-            assert move_line.debit == 216.00, "Bank entry is wrong."
+        if float_compare(move_line.amount_currency, -180.00, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 162.00, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.amount_currency, -70.00, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 63.00, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.amount_currency, 10.00, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 9.00, precision_digits=2) == 0, "Writeoff amount is wrong."
+        elif float_compare(move_line.amount_currency, 240.00, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 216.00, precision_digits=2) == 0, "Bank entry is wrong."
         else:
             assert False, "Unrecognized journal entry"
 -
   I check the residual amount of Invoice1, should be 20 in amount_currency
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_jan"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 20.0) , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 20.0, precision_digits=2) == 0) , "Residual amount is not correct for first Invoice"
 -
   I check the residual amuont of Invoice2, should be 30 in residual currency and 24 in amount_residual
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_feb"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 30.0) , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 30.0, precision_digits=2) == 0) , "Residual amount is not correct for first Invoice"
 -
   On the first April, I create the second voucher of payment with values 45 USD, journal USD, 
 -
   I fill amounts 20 for the invoice of 200$ and 30 for the invoice of 100$
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_2_case1'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 20.00:
+        if float_compare(item.amount_unreconciled, 20.00, precision_digits=2) == 0:
             data += [(item.id, 20.0)]
         else:
             data += [(item.id, 30.0)]
   I check that writeoff amount computed is -5.0
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 1'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (voucher_id.writeoff_amount == -5.0), "Writeoff amount is not -5.0"
+    assert (float_compare(voucher_id.writeoff_amount, -5.0, precision_digits=2) == 0), "Writeoff amount is not -5.0"
 -
   I confirm the voucher
 -
   I check that my writeoff is correct. 4.75 debit and 5 amount_currency
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 1'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     reconcile_a = reconcile_b = False
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.amount_currency == -20.00:
+        if float_compare(move_line.amount_currency, -20.00, precision_digits=2) == 0:
             assert move_line.reconcile_id.id, "The invoice of 200$ is not fully reconciled"
             reconcile_b = move_line.reconcile_id.id
-        elif move_line.amount_currency == -30.00:
+        elif float_compare(move_line.amount_currency, -30.00, precision_digits=2) == 0:
             assert move_line.reconcile_id.id, "The invoice of 100$ is not fully reconciled"
             reconcile_a = move_line.reconcile_id.id
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.amount_currency == -20.00:
-            assert move_line.credit == 19.00, "Debtor account has wrong entry."
-        elif move_line.amount_currency == -30.00:
-            assert move_line.credit == 28.50, "Debtor account has wrong entry."
-        elif move_line.amount_currency == 5.00:
-            assert move_line.debit == 4.75, "Writeoff amount is wrong."
-        elif move_line.debit == 11.5 and move_line.account_id.reconcile:
-            assert move_line.amount_currency == 0.0 and move_line.reconcile_id.id == reconcile_a, "Exchange difference entry for the invoice of 100$ is wrong"
-        elif move_line.credit == 11.5:
-            assert move_line.amount_currency == 0.0
-        elif move_line.debit == 31.0 and move_line.account_id.reconcile:
-            assert move_line.amount_currency == 0.0 and move_line.reconcile_id.id == reconcile_b, "Exchange difference entry for the invoice of 200$ is wrong"
-        elif move_line.credit == 31.0:
-            assert move_line.amount_currency == 0.0
-        elif move_line.amount_currency == 45.00:
-            assert move_line.debit == 42.75, "Bank entry is wrong."
+        if float_compare(move_line.amount_currency, -20.00, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 19.00, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.amount_currency, -30.00, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 28.50, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.amount_currency, 5.00, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 4.75, precision_digits=2) == 0, "Writeoff amount is wrong."
+        elif float_compare(move_line.debit, 11.5, precision_digits=2) == 0 and move_line.account_id.reconcile:
+            assert float_compare(move_line.amount_currency, 0.0, precision_digits=2) == 0 and move_line.reconcile_id.id == reconcile_a, "Exchange difference entry for the invoice of 100$ is wrong"
+        elif float_compare(move_line.credit, 11.5, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 0.0, precision_digits=2) == 0
+        elif float_compare(move_line.debit, 31.0, precision_digits=2) == 0 and move_line.account_id.reconcile:
+            assert float_compare(move_line.amount_currency, 0.0, precision_digits=2) == 0 and move_line.reconcile_id.id == reconcile_b, "Exchange difference entry for the invoice of 200$ is wrong"
+        elif float_compare(move_line.credit, 31.0, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 0.0, precision_digits=2) == 0
+        elif float_compare(move_line.amount_currency, 45.00, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 42.75, precision_digits=2) == 0, "Bank entry is wrong."
         else:
             assert False, "Unrecognized journal entry"
 -
   I check the residual amount of Invoice1, should be 0 in residual currency and 0 in amount_residual and paid
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_jan"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for first Invoice"
 -
   I check the residual amuont of Invoice2, should be 0 in residual currency and 0 in amount_residual and paid
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_feb"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for second Invoice"
index c0aa617..7f5cf15 100644 (file)
   I check that first invoice move is correct for debtor account (debit - credit == 150.0)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_jan_payment_rate"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == 150.0), "Invoice move is not correct for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 150.0, precision_digits=2) == 0), "Invoice move is not correct for debtors account"
 -
   I create the second invoice on 1st February for 100 USD
 -
   I check that second invoice move is correct for debtor account (debit - credit == 80)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_feb_payment_rate"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == 80), "Invoice move is not correct for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 80, precision_digits=2) == 0), "Invoice move is not correct for debtors account"
 
 -
   I set the context that will be used for the encoding of all the vouchers of this file
   I fill amounts 180 for the invoice of 200$ and 70 for the invoice of 100$>
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_1_case1_payment_rate'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 200.00:
+        if float_compare(item.amount_unreconciled, 200.00, precision_digits=2) == 0:
             data += [(item.id, 180.0)]
         else:
             data += [(item.id, 70.0)]
   I check that writeoff amount computed is -10.0
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = ref('account_voucher_1_case1_payment_rate') 
     voucher_id = self.browse(cr, uid, voucher)
-    assert (voucher_id.writeoff_amount == -10.0), "Writeoff amount is not -10.0"
+    assert (float_compare(voucher_id.writeoff_amount, -10.0, precision_digits=2) == 0), "Writeoff amount is not -10.0"
 -
   I confirm the voucher
 -
   I check that my write-off is correct. 8 debit and 10 amount_currency
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = ref('account_voucher_1_case1_payment_rate') 
     voucher_id = self.browse(cr, uid, voucher)
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.amount_currency == -180.00:
-            assert move_line.credit == 144.00, "Debtor account has wrong entry."
-        elif move_line.amount_currency == -70.00:
-            assert move_line.credit == 56.00, "Debtor account has wrong entry."
-        elif move_line.amount_currency == 10.00:
-            assert move_line.debit == 8.00, "Writeoff amount is wrong: Got a debit of %s (expected 8,00€)" % (move_line.debit)
-        elif move_line.amount_currency == 240.00:
-            assert move_line.debit == 192.00, "Bank entry is wrong."
+        if float_compare(move_line.amount_currency, -180.00, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 144.00, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.amount_currency, -70.00, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 56.00, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.amount_currency, 10.00, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 8.00, precision_digits=2) == 0, "Writeoff amount is wrong: Got a debit of %s (expected 8,00€)" % (move_line.debit)
+        elif float_compare(move_line.amount_currency, 240.00, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 192.00, precision_digits=2) == 0, "Bank entry is wrong."
         else:
             assert False, "Unrecognized journal entry"
 -
   I check the residual amount of Invoice1, should be 20 in amount_currency and 6 in amount_residual
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_jan_payment_rate"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual == 6.0) , "Residual amount is not correct for first Invoice"
-    assert (move_line.amount_residual_currency == 20.0) , "Residual amount in currency is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual, 6.0, precision_digits=2) == 0) , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 20.0, precision_digits=2) == 0) , "Residual amount in currency is not correct for first Invoice"
 -
   I check the residual amuont of Invoice2, should be 30 in residual currency and 24 in amount_residual
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_feb_payment_rate"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual == 24.0) , "Residual amount is not correct for second Invoice"
-    assert (move_line.amount_residual_currency == 30.0) , "Residual amount in currency is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual, 24.0, precision_digits=2) == 0) , "Residual amount is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 30.0, precision_digits=2) == 0) , "Residual amount in currency is not correct for second Invoice"
index 836bee3..79870ae 100644 (file)
   I check that first invoice move is correct for debtor account(debit - credit == -150)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan_suppl"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == -150.00), "Invoice move is incorrect for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, -150.00, precision_digits=2) == 0), "Invoice move is incorrect for debtors account"
 -
   I create the second invoice on 1st February for 100 USD
 -
   I check that second invoice move is correct for debtor account (debit - credit == -80)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb_suppl"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == -80), "Invoice move is incorrect for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, -80, precision_digits=2) == 0), "Invoice move is incorrect for debtors account"
 -
   I set the context that will be used for the encoding of all the vouchers of this file
 -
   I fill amounts 180 for the invoice of 200$ and 70 for the invoice of 100$
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_1_case2_suppl'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 200.00:
+        if float_compare(item.amount_unreconciled, 200.00, precision_digits=2) == 0:
             data += [(item.id, 180.0)]
         else:
             data += [(item.id, 70.0)]
   I check that writeoff amount computed is -15.0
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 2 SUPPL USD/EUR'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (voucher_id.writeoff_amount == -15.0), "Writeoff amount is not -15.0"
+    assert (float_compare(voucher_id.writeoff_amount, -15.0, precision_digits=2) == 0), "Writeoff amount is not -15.0"
 -
   I check that currency rate difference is 34.0
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 2 SUPPL USD/EUR'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (voucher_id.currency_rate_difference == 34.0), "Currency rate difference is not 34.0"
+    assert (float_compare(voucher_id.currency_rate_difference, 34.0, precision_digits=2) == 0), "Currency rate difference is not 34.0"
 -
   I confirm the voucher
 -
   I check that my writeoff is correct. -15 in credit with no amount_currency
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 2 SUPPL USD/EUR'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.amount_currency == 180.00:
-            assert move_line.debit == 135.00, "Creditor account has wrong entry."
-        elif move_line.amount_currency == 70.00:
-            assert move_line.debit == 56.00, "Debtor account has wrong entry."
-        elif move_line.debit == 34.00:
-            assert move_line.amount_currency == 0.00, "Incorrect Currency Difference."
-        elif move_line.debit == 15.00:
-            assert move_line.amount_currency == 0.00, "Writeoff amount is wrong."
+        if float_compare(move_line.amount_currency, 180.00, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 135.00, precision_digits=2) == 0, "Creditor account has wrong entry."
+        elif float_compare(move_line.amount_currency, 70.00, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 56.00, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.debit, 34.00, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0, "Incorrect Currency Difference."
+        elif float_compare(move_line.debit, 15.00, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0, "Writeoff amount is wrong."
 -
   I check the residual amount of Invoice1, should be 20 in residual currency and 15 in amount_residual
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan_suppl"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 20.0 and move_line.amount_residual == 15) , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 20.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 15, precision_digits=2) == 0) , "Residual amount is not correct for first Invoice"
 -
   I check the residual amuont of Invoice2, should be 30 in residual currency and 24 in amount_residual
 -
    !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb_suppl"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 30 and move_line.amount_residual == 24) , "Residual amount is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 30, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 24, precision_digits=2) == 0) , "Residual amount is not correct for second Invoice"
 -
   I create the second voucher of payment with values 45 USD, journal USD, 
 -
   I fill amounts 20 for the invoice of 200$ and 30 for the invoice of 100$>
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_2_case2_suppl'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 20.00:
+        if float_compare(item.amount_unreconciled, 20.00, precision_digits=2) == 0:
             data += [(item.id, 20.0)]
         else:
             data += [(item.id, 30.0)]
   I check that writeoff amount computed is -5.0
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 2 SUPPL USD/EUR'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (voucher_id.writeoff_amount == 5.0), "Writeoff amount is not 5.0"
+    assert (float_compare(voucher_id.writeoff_amount, 5.0, precision_digits=2) == 0), "Writeoff amount is not 5.0"
 -
   I check that currency rate difference is 8.50
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 2 SUPPL USD/EUR'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (voucher_id.currency_rate_difference == 8.50), "Currency rate difference is not 8.50"
+    assert (float_compare(voucher_id.currency_rate_difference, 8.50, precision_digits=2) == 0), "Currency rate difference is not 8.50"
 -
   I confirm the voucher
 -
   I check that my writeoff is correct. 4.75 in credit and 5 in amount_currency
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 2 SUPPL USD/EUR'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.amount_currency == 20.00:
-            assert move_line.debit == 15.00, "Debtor account has wrong entry."
-        elif move_line.amount_currency == 30.00:
-            assert move_line.debit == 24.00, "Debtor account has wrong entry."
-        elif move_line.debit == 8.50:
-            assert move_line.amount_currency == 0.00, "Incorrect Currency Difference."
-        elif move_line.amount_currency == -5.00:
-            assert move_line.credit == 4.75, "Writeoff amount is wrong."
+        if float_compare(move_line.amount_currency, 20.00, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 15.00, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.amount_currency, 30.00, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 24.00, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.debit, 8.50, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0, "Incorrect Currency Difference."
+        elif float_compare(move_line.amount_currency, -5.00, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 4.75, precision_digits=2) == 0, "Writeoff amount is wrong."
 -
   I check the residual amount of invoice 1, should be 0 in residual currency and 0 in amount_residual and paid
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan_suppl"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for first Invoice"
 -    
   I check the residual amount of invoice 2, should be 0 in residual currency and 0 in amount_residual and paid
 -
    !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb_suppl"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for second Invoice"
index 45c86eb..0eb71d6 100644 (file)
   I check that first invoice move is correct for debtor account(debit - credit == 150)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == 150.00), "Invoice move is incorrect for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 150.00, precision_digits=2) == 0), "Invoice move is incorrect for debtors account"
 -
   I create the second invoice on 1st February for 100 USD
 -
   I check that second invoice move is correct for debtor account (debit - credit == 80)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == 80), "Invoice move is incorrect for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 80, precision_digits=2) == 0), "Invoice move is incorrect for debtors account"
 -
   I set the context that will be used for the encoding of all the vouchers of this file
 -
   I fill amounts 130 for the invoice of 200$ and 70 for the invoice of 100$
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     import time
     from openerp import netsvc
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_1_case2a'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 150.00:
+        if float_compare(item.amount_unreconciled, 150.00, precision_digits=2) == 0:
             data += [(item.id, 130.0)]
         else:
             data += [(item.id, 70.0)]
   I check the residual amount of Invoice1, should be 55.56 in residual currency and 20 in amount_residual
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 55.56 and move_line.amount_residual == 20) , "Residual amount is not correct for first Invoice. Got %s USD (%s EUR)" %(move_line.amount_residual_currency, move_line.amount_residual)
+    assert (float_compare(move_line.amount_residual_currency, 55.56, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 20, precision_digits=2) == 0) , "Residual amount is not correct for first Invoice. Got %s USD (%s EUR)" %(move_line.amount_residual_currency, move_line.amount_residual)
 -
   I check the residual amuont of Invoice2, should be 22.22 in residual currency and 10 in amount_residual
 -
    !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 22.22 and move_line.amount_residual == 10) , "Residual amount is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 22.22, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 10, precision_digits=2) == 0) , "Residual amount is not correct for second Invoice"
 -
   I create the second voucher of payment with values 80 USD, journal USD
 -
   !python {model: account.voucher}: |
     import time
     from openerp import netsvc
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_2_case2a'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 55.56:
+        if float_compare(item.amount_unreconciled, 55.56, precision_digits=2) == 0:
             data += [(item.id, 55.56)]
         else:
             data += [(item.id, 22.22)]
   I check that writeoff amount computed is 2.22
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 2 SUPPL USD/EUR DR EUR'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (round(voucher_id.writeoff_amount, 2) == 2.22), "Writeoff amount is not 2.22$"
+    assert (float_compare(round(voucher_id.writeoff_amount, 2), 2.22, precision_digits=2) == 0), "Writeoff amount is not 2.22$"
 -
   I confirm the voucher
 -
   I check that my writeoff is correct. 2.11 in credit and 2.22 in amount_currency
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 2 SUPPL USD/EUR DR EUR'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     reconcile_a = reconcile_b = False
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.amount_currency == -22.22:
+        if float_compare(move_line.amount_currency, -22.22, precision_digits=2) == 0:
             assert move_line.reconcile_id.id, "The invoice of 200$ is not fully reconciled"
             reconcile_b = move_line.reconcile_id.id
-        elif move_line.amount_currency == -55.56:
+        elif float_compare(move_line.amount_currency, -55.56, precision_digits=2) == 0:
             assert move_line.reconcile_id.id, "The invoice of 100$ is not fully reconciled"
             reconcile_a = move_line.reconcile_id.id
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.amount_currency == -55.56:
-            assert move_line.credit == 52.78, "Debtor account has wrong entry."
-        elif move_line.amount_currency == -22.22:
-            assert move_line.credit == 21.11, "Debtor account has wrong entry."
-        elif move_line.credit == 11.11 and move_line.account_id.reconcile:
-            assert move_line.amount_currency == 0.00 and move_line.reconcile_id.id == reconcile_b, "Incorrect Currency Difference."
-        elif move_line.credit == 32.78 and move_line.account_id.reconcile:
-            assert move_line.amount_currency == 0.00 and move_line.reconcile_id.id == reconcile_a, "Incorrect Currency Difference."
-        elif move_line.amount_currency == 2.22:
-            assert move_line.credit == 2.11, "Writeoff amount is wrong."
+        if float_compare(move_line.amount_currency, -55.56, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 52.78, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.amount_currency, -22.22, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 21.11, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.credit, 11.11, precision_digits=2) == 0 and move_line.account_id.reconcile:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0 and move_line.reconcile_id.id == reconcile_b, "Incorrect Currency Difference."
+        elif float_compare(move_line.credit, 32.78, precision_digits=2) == 0 and move_line.account_id.reconcile:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0 and move_line.reconcile_id.id == reconcile_a, "Incorrect Currency Difference."
+        elif float_compare(move_line.amount_currency, 2.22, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 2.11, precision_digits=2) == 0, "Writeoff amount is wrong."
 -
   I check the residual amount of invoice 1, should be 0 in residual currency and 0 in amount_residual and paid
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for first Invoice"
 -    
   I check the residual amuont of invoice 2, should be 0 in residual currency and 0 in amount_residual and paid
 -
    !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for second Invoice"
index f353cd8..12d6f07 100644 (file)
   I check that first invoice move is correct for debtor account(debit - credit == 150)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan_michal"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == 150.00), "Invoice move is incorrect for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 150.00, precision_digits=2) == 0), "Invoice move is incorrect for debtors account"
 -
   I create the second invoice on 1st February for 100 USD
 -
   I check that second invoice move is correct for debtor account (debit - credit == 80)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb_michal"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == 80), "Invoice move is incorrect for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 80, precision_digits=2) == 0), "Invoice move is incorrect for debtors account"
 -
   I set the context that will be used for the encoding of all the vouchers of this file
 -
   I fill amounts 130 for the invoice of 200$ and 70 for the invoice of 100$>
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_1_case2b'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 150.00:
+        if float_compare(item.amount_unreconciled, 150.00, precision_digits=2) == 0:
             data += [(item.id, 130.0)]
         else:
             data += [(item.id, 70.0)]
   I check that the debtor account has 2 new lines with 144.44 and 77.78 in amount_currency columns and their credit columns are 130 and 70
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 2 USD/EUR DR USD'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.credit == 70.0:
-            assert move_line.amount_currency == -77.78, "Wrong debtor entry"
-        if move_line.credit == 130.0:
-            assert move_line.amount_currency == -144.44, "Wrong debtor entry"
+        if float_compare(move_line.credit, 70.0, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, -77.78, precision_digits=2) == 0, "Wrong debtor entry"
+        if float_compare(move_line.credit, 130.0, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, -144.44, precision_digits=2) == 0, "Wrong debtor entry"
 -
   I check the residual amount of Invoice1, should be 55.56 in residual currency and 20 in amount_residual
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan_michal"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 55.56 and move_line.amount_residual == 20) , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 55.56, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 20, precision_digits=2) == 0) , "Residual amount is not correct for first Invoice"
 -
   I check the residual amount of Invoice2, should be 22.22 in residual currency and 10 in amount_residual
 -
    !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb_michal"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 22.22 and move_line.amount_residual == 10) , "Residual amount is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 22.22, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 10, precision_digits=2) == 0) , "Residual amount is not correct for second Invoice"
 -
   I create the second voucher of payment with values 80 USD, journal USD
 -
   and I fully reconcil the 2 previous invoices
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_2_case2b'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 55.56:
+        if float_compare(item.amount_unreconciled, 55.56, precision_digits=2) == 0:
             data += [(item.id, 55.56)]
         else:
             data += [(item.id, 22.22)]
   I check that writeoff amount computed is 2.22
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 2 SUPPL USD/EUR DR USD'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (round(voucher_id.writeoff_amount, 2) == 2.22), "Writeoff amount is not 2.22$"
+    assert (float_compare(round(voucher_id.writeoff_amount, 2), 2.22, precision_digits=2) == 0), "Writeoff amount is not 2.22$"
 -
   I confirm the voucher
 -
   I check that my writeoff is correct. 2.11 in credit and 2.22 in amount_currency
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 2 SUPPL USD/EUR DR USD'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     reconcile_a = reconcile_b = False
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.amount_currency == -22.22:
+        if float_compare(move_line.amount_currency, -22.22, precision_digits=2) == 0:
             assert move_line.reconcile_id.id, "The invoice of 200$ is not fully reconciled"
             reconcile_b = move_line.reconcile_id.id
-        elif move_line.amount_currency == -55.56:
+        elif float_compare(move_line.amount_currency, -55.56, precision_digits=2) == 0:
             assert move_line.reconcile_id.id, "The invoice of 100$ is not fully reconciled"
             reconcile_a = move_line.reconcile_id.id
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.amount_currency == -55.56:
-            assert move_line.credit == 52.78, "Debtor account has wrong entry."
-        elif move_line.amount_currency == -22.22:
-            assert move_line.credit == 21.11, "Debtor account has wrong entry."
-        elif move_line.credit == 11.11 and move_line.account_id.reconcile:
-            assert move_line.amount_currency == 0.00 and move_line.reconcile_id.id == reconcile_b, "Incorrect Currency Difference."
-        elif move_line.credit == 32.78 and move_line.account_id.reconcile:
-            assert move_line.amount_currency == 0.00 and move_line.reconcile_id.id == reconcile_a, "Incorrect Currency Difference."
-        elif move_line.amount_currency == 2.22:
-            assert move_line.credit == 2.11, "Writeoff amount is wrong."
+        if float_compare(move_line.amount_currency, -55.56, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 52.78, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.amount_currency, -22.22, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 21.11, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.credit, 11.11, precision_digits=2) == 0 and move_line.account_id.reconcile:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0 and move_line.reconcile_id.id == reconcile_b, "Incorrect Currency Difference."
+        elif float_compare(move_line.credit, 32.78, precision_digits=2) == 0 and move_line.account_id.reconcile:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0 and move_line.reconcile_id.id == reconcile_a, "Incorrect Currency Difference."
+        elif float_compare(move_line.amount_currency, 2.22, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 2.11, precision_digits=2) == 0, "Writeoff amount is wrong."
 -
   I check the residual amount of invoice 1, should be 0 in residual currency and 0 in amount_residual and paid
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan_michal"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for first Invoice"
 -
   I check the residual amount of invoice 2, should be 0 in residual currency and 0 in amount_residual and paid
 -
    !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb_michal"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for second Invoice"
index 064b79e..6c48c2e 100644 (file)
   I check that first invoice move is correct for debtor account(debit - credit == 150)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan_eur"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == 150.00), "Invoice move is incorrect for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 150.00, precision_digits=2) == 0), "Invoice move is incorrect for debtors account"
 -
   I create the second invoice on 1st February for 80 EUR
 -
   I check that second invoice move is correct for debtor account (debit - credit == 80)
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb_eur"))
     assert invoice_id.move_id, "Move not created for open invoice"
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.debit - move_line.credit == 80.00), "Invoice move is incorrect for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 80.00, precision_digits=2) == 0), "Invoice move is incorrect for debtors account"
 -
   I set the context that will be used for the encoding of all the vouchers of this file
 -
   I fill amounts 100 for the invoice of 150€ and 20 for the invoice of 80€
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_1_case3'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 150.00:
+        if float_compare(item.amount_unreconciled, 150.00, precision_digits=2) == 0:
             data += [(item.id, 100.0)]
         else:
             data += [(item.id, 20.0)]
   I check that writeoff amount computed is 0.00
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 3'),('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (voucher_id.writeoff_amount == 0.00), "Writeoff amount is not 0.00"
+    assert (float_compare(voucher_id.writeoff_amount, 0.00, precision_digits=2) == 0), "Writeoff amount is not 0.00"
 -
   I confirm the voucher
 -
   I check that the debtor account has 2 new lines with 0.00 and 0.00 in amount_currency columns and their credit are 20 and 100 respectively
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 3'),('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.credit == 20.00:
-            assert move_line.amount_currency == 0.00, "Debtor account has wrong entry."
-        elif move_line.credit == 100.00:
-            assert move_line.amount_currency == 0.00, "Debtor account has wrong entry."
+        if float_compare(move_line.credit, 20.00, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.credit, 100.00, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0, "Debtor account has wrong entry."
 -
   I check the residual amount of Invoice1 is 50
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan_eur"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 50.0 and move_line.amount_residual == 50.0) , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 50.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 50.0, precision_digits=2) == 0) , "Residual amount is not correct for first Invoice"
 -
   I check the residual amuont of Invoice2 is 60
 -
    !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb_eur"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 60.0 and move_line.amount_residual == 60.0) , "Residual amount is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 60.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 60.0, precision_digits=2) == 0) , "Residual amount is not correct for second Invoice"
 -
   I create the second voucher of payment with values 120€, journal EUR, and check to let open the debtor overpaid amount
 -
   I fill amounts 50 for the invoice of 150€ and 70 for the invoice of 80€
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_2_case3'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 50.00:
+        if float_compare(item.amount_unreconciled, 50.00, precision_digits=2) == 0:
             data += [(item.id, 50.0)]
-        elif item.amount_unreconciled == 60.00:
+        elif float_compare(item.amount_unreconciled, 60.00, precision_digits=2) == 0:
             data += [(item.id, 70.00)]
     for line_id, amount in data:
         self.pool.get('account.voucher.line').write(cr, uid, [line_id], {'amount': amount})
   I check that writeoff amount computed is 0.00
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 3'),('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (voucher_id.writeoff_amount == 0.00), "Writeoff amount is not 0"
+    assert (float_compare(voucher_id.writeoff_amount, 0.00, precision_digits=2) == 0), "Writeoff amount is not 0"
 -
   I confirm the voucher
 -
   I check that the debtor account has 2 new lines with 0.00 and 0.00 in amount_currency columns and their credit are 70 and 50
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'Second payment: Case 3'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if move_line.credit == 70.00:
-            assert move_line.amount_currency == 0.00, "Debtor account has wrong entry."
-        elif move_line.credit == 50.00:
-            assert move_line.amount_currency == 0.00, "Debtor account has wrong entry."
+        if float_compare(move_line.credit, 70.00, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.credit, 50.00, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0, "Debtor account has wrong entry."
 -
   I check the residual amount of Invoice1 is 0
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan_eur"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0 and move_line.amount_residual == 0) , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0, precision_digits=2) == 0) , "Residual amount is not correct for first Invoice"
 -    
   I check the residual amuont of Invoice2 is -10
 -
    !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_second_invoice_feb_eur"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == -10.0 and move_line.amount_residual == -10.0) , "Residual amount is not correct for second Invoice"
+    assert (float_compare(move_line.amount_residual_currency, -10.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, -10.0, precision_digits=2) == 0) , "Residual amount is not correct for second Invoice"
index 476a22c..e31dd57 100644 (file)
@@ -94,7 +94,7 @@
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert float_compare(move_line.debit - move_line.credit, 149.39, 2) == 0, "Invoice move is incorrect for debtors account"
+    assert (float_compare(move_line.debit - move_line.credit, 149.39, precision_digits=2) == 0), "Invoice move is incorrect for debtors account"
 -
   I set the context that will be used for the encoding of all the vouchers of this file
 -
   !python {model: account.voucher}: |
     import time
     from openerp import netsvc
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_1_case4'))
     data = []
     for item in voucher_id.line_cr_ids:
-        if item.amount_unreconciled == 186.74:
+        if float_compare(item.amount_unreconciled, 186.74, precision_digits=2) == 0:
             data += [(item.id, 186.74)]
         else:
             data += [(item.id, 0.0)]
   I check that writeoff amount computed is 13.26
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 4'), ('partner_id', '=', ref('base.res_partner_19'))])
     voucher_id = self.browse(cr, uid, voucher[0])
-    assert (round(voucher_id.writeoff_amount,2) == 13.26), "Writeoff amount is not 13.26 CHF"
+    assert (float_compare(round(voucher_id.writeoff_amount,2), 13.26, precision_digits=2) == 0), "Writeoff amount is not 13.26 CHF"
 -
   I confirm the voucher
 -
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', voucher_id.move_id.id)])
     assert move_lines, "Voucher move has no lines"
     for move_line in move_line_obj.browse(cr, uid, move_lines):
-        if float_compare(move_line.amount_currency, 200, 2) == 0:
-            assert float_compare(move_line.debit, 160.00, 2) == 0, "Bank account has wrong entry."
-        elif float_compare(move_line.amount_currency, -298.78, 2) == 0:
-            assert float_compare(move_line.credit, 149.39, 2) == 0, "Debtor account has wrong entry."
-        elif move_line.debit == 0.00 and move_line.credit == 0.00:
-            assert move_line.amount_currency == 98.78, "Incorrect Currency Difference, got %s as amount_currency (expected 98.78)." % (move_line.amount_currency)
+        if float_compare(move_line.amount_currency, 200, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 160.00, precision_digits=2) == 0, "Bank account has wrong entry."
+        elif float_compare(move_line.amount_currency, -298.78, precision_digits=2) == 0 :
+            assert float_compare(move_line.credit, 149.39, precision_digits=2) == 0, "Debtor account has wrong entry."
+        elif float_compare(move_line.debit, 0.00, precision_digits=2) == 0 and float_compare(move_line.credit, 0.00, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 98.78, precision_digits=2) == 0, "Incorrect Currency Difference, got %s as amount_currency (expected 98.78)." % (move_line.amount_currency)
             assert move_line.currency_id.id == ref('base.CAD'), "Incorrect Currency Difference, got %s (expected 'CAD')" % (move_line.currency_id.name)
-        elif move_line.credit == 10.61:
-            assert move_line.amount_currency == -13.26, "Writeoff amount is wrong."
+        elif float_compare(move_line.credit, 10.61, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, -13.26, precision_digits=2) == 0, "Writeoff amount is wrong."
         else:
             assert False, "Wrong entry"
 -
   I check the residual amount of Invoice1, should be 0 in residual currency and 0 in amount_residual and paid
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_first_invoice_jan_cad"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for first Invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for first Invoice"
index c20d07a..327cd9d 100644 (file)
 -
   !record {model: res.currency.rate, id: nov_usd}:
     currency_id: base.USD
-    name: !eval "'%s-11-01 00:00:00' %(datetime.now().year)"
+    name: !eval "'%s-11-01' %(datetime.now().year)"
     rate: 1.8
 -
   I create currency USD in OpenERP for December of 1.5 Rate
 -
   !record {model: res.currency.rate, id: dec_usd}:
     currency_id: base.USD
-    name: !eval "'%s-12-01 00:00:00' %(datetime.now().year)"
+    name: !eval "'%s-12-01' %(datetime.now().year)"
     rate: 1.5
 -
   I set the income and expense currency accounts on the main company
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert float_compare(move_line.debit - move_line.credit, -555.56, 2) == 0, \
-           "Invoice move is incorrect for creditor account"
-    assert float_compare(move_line.amount_currency, -1000, 2) == 0, \
-           "Amount currency is incorrect for creditor account"
+    assert (float_compare(move_line.debit - move_line.credit, -555.56, precision_digits=2) == 0), "Invoice move is incorrect for creditor account"
+    assert (float_compare(move_line.amount_currency, -1000, precision_digits=2) == 0), "Amount currency is incorrect for creditor account"
 -
   I set the context that will be used for the encoding of all the vouchers of this file
 -
 -
   !python {model: account.voucher}: |
     import time
+    from openerp.tools import float_compare
     vals = {}
     voucher_id = self.browse(cr, uid, ref('account_voucher_case_5_supplier_flow'))
     for item in voucher_id.line_dr_ids:
-        if item.amount_unreconciled == 1000.00:
+        if float_compare(item.amount_unreconciled, 1000.00, precision_digits=2) == 0:
             self.pool.get('account.voucher.line').write(cr, uid, [item.id], {'amount': 1000})
     assert (voucher_id.state=='draft'), "Voucher is not in draft state"
 -
   I check that writeoff amount computed is -50.0
 -
-  !assert {model: account.voucher, id: account_voucher_case_5_supplier_flow}:
-    - writeoff_amount == -50.0
+  !python {model: account.voucher}: |
+    from openerp.tools import float_compare
+    writeoff_amount = self.browse(cr, uid, ref('account_voucher_case_5_supplier_flow')).writeoff_amount
+    assert float_compare(writeoff_amount, -50.0, precision_digits=2) == 0
 -
   I confirm the voucher
 -
   I check that my writeoff is correct. 33.34€ in credit with -$50 as amount currency
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher_id = self.browse(cr, uid, ref('account_voucher_case_5_supplier_flow'))
     for move_line in voucher_id.move_id.line_id:
-        if move_line.amount_currency == -950.00:
-            assert move_line.credit == 633.33, "Wrong bank entry."
-        elif move_line.credit == 111.11 or move_line.debit == 111.11:
-            assert move_line.amount_currency == 0.00, "Incorrect Currency Difference."
-        elif move_line.credit == 33.34:
-            assert move_line.amount_currency == -50.0, "Writeoff amount is wrong."
-        elif move_line.debit == 666.67:
-            assert move_line.amount_currency == 1000.0, "Wrong supplier entry."
+        if float_compare(move_line.amount_currency, -950.00, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 633.33, precision_digits=2) == 0, "Wrong bank entry."
+        elif float_compare(move_line.credit, 111.11, precision_digits=2) == 0 or float_compare(move_line.debit, 111.11, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 0.00, precision_digits=2) == 0, "Incorrect Currency Difference."
+        elif float_compare(move_line.credit, 33.34, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, -50.0, precision_digits=2) == 0, "Writeoff amount is wrong."
+        elif float_compare(move_line.debit, 666.67, precision_digits=2) == 0:
+            assert float_compare(move_line.amount_currency, 1000.0, precision_digits=2) == 0, "Wrong supplier entry."
         else:
             assert False, "Wrong entry. Unrecognized account move line"
 -
   I check the residual amount of invoice, it should be 0 in residual currency and 0 in amount_residual and paid
 -
    !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_supplier_invoice_november"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert (move_line.amount_residual_currency == 0.0 and move_line.amount_residual == 0.0 and invoice_id.state == 'paid') , "Residual amount is not correct for supplier invoice"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for supplier invoice"
index 69304c3..ce2b71d 100644 (file)
   I fill amount 1400 for the invoice of 1400$
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     data = []
     voucher = self.browse(cr, uid, ref('account_voucher_eur_usd_case'))
     for item in voucher.line_cr_ids:
-        if item.amount_unreconciled == 1400:
+        if float_compare(item.amount_unreconciled, 1400, precision_digits=2) == 0:
             data += [(item.id, 1400)]
     assert data, "Credit line not found"
     for line_id, amount in data:
   I check that writeoff amount computed is -50.0
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.browse(cr, uid, ref('account_voucher_eur_usd_case'))
-    assert (voucher.writeoff_amount == -50.0), "Writeoff amount is not -50.0"
+    assert (float_compare(voucher.writeoff_amount, -50.0, precision_digits=2) == 0), "Writeoff amount is not -50.0"
 -
   I confirm the voucher
 -
   I check that my bank account is correct. 964.29 debit and 1350 amount_currency
 -
   !python {model: account.voucher}: |
+    from openerp.tools import float_compare
     voucher = self.browse(cr, uid, ref('account_voucher_eur_usd_case'))
     for move_line in voucher.move_ids:
-        if move_line.amount_currency == 1350:
-            assert move_line.debit == 964.29,"debtor account is not correct"
-        if move_line.amount_currency == 50:
-            assert move_line.debit == 35.71,"write off bank account is not correct"
-        if move_line.amount_currency == 0.0:
-            assert move_line.credit == 1000,"total reconcile is not correct of invoice"
+        if float_compare(move_line.amount_currency, 1350, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 964.29, precision_digits=2) == 0,"debtor account is not correct"
+        if float_compare(move_line.amount_currency, 50, precision_digits=2) == 0:
+            assert float_compare(move_line.debit, 35.71, precision_digits=2) == 0,"write off bank account is not correct"
+        if float_compare(move_line.amount_currency, 0.0, precision_digits=2) == 0:
+            assert float_compare(move_line.credit, 1000, precision_digits=2) == 0,"total reconcile is not correct of invoice"
 -
   I check that the move of payment in invoice is valid
 -
   I check the residual amount of invoice should be 0 in residual currency and 0 in amount_residual and paid
 -
   !python {model: account.invoice}: |
+    from openerp.tools import float_compare
     invoice_id = self.browse(cr, uid, ref("account_invoice_eur_usd"))
     move_line_obj = self.pool.get('account.move.line')
     move_lines = move_line_obj.search(cr, uid, [('move_id', '=', invoice_id.move_id.id), ('invoice', '=', invoice_id.id), ('account_id', '=', invoice_id.account_id.id)])
     move_line = move_line_obj.browse(cr, uid, move_lines[0])
-    assert move_line.amount_residual_currency == 0.0, "Residual amount is not correct for Invoice"
-    assert move_line.amount_residual == 0.0, "Residual amount is not correct for Invoice"
-    assert invoice_id.state == 'paid', "Invoice is not paid"
+    assert (float_compare(move_line.amount_residual_currency, 0.0, precision_digits=2) == 0 and float_compare(move_line.amount_residual, 0.0, precision_digits=2) == 0 and invoice_id.state == 'paid') , "Residual amount is not correct for Invoice"
index 1bd537a..f05376a 100644 (file)
@@ -8,9 +8,9 @@
             <field name="inherit_id" ref="base.view_partner_form"/>
             <field name="arch" type="xml">
                 <xpath expr="//notebook[last()]" position="inside">
-                    <page string="Geo Localization" name="geo_localization">
+                    <page string="Geo Location" name="geo_location">
                         <group colspan="2" col="2">
-                            <separator string="Geo Localization" colspan="2"/>
+                            <separator string="Geo Location" colspan="2"/>
                             <button
                                 string="Geo Localize"
                                 name="geo_localize"
index a07c730..011a6ff 100644 (file)
@@ -56,8 +56,6 @@ class res_partner(osv.osv):
     _columns = {
         'partner_weight': fields.integer('Grade Weight',
             help="Gives the probability to assign a lead to this partner. (0 means no assignation.)"),
-        'opportunity_assigned_ids': fields.one2many('crm.lead', 'partner_assigned_id',\
-            'Assigned Opportunities'),
         'grade_id': fields.many2one('res.partner.grade', 'Grade'),
         'activation' : fields.many2one('res.partner.activation', 'Activation', select=1),
         'date_partnership' : fields.date('Partnership Date'),
index 4d613c5..98c00a9 100644 (file)
         <field name="model">res.partner</field>
         <field name="inherit_id" ref="base_geolocalize.view_crm_partner_geo_form"/>
         <field name="arch" type="xml">
-            <xpath expr="//page[@name='geo_localization']" position="replace">
-            <!-- <xpath expr="//notebook[last()]" position="inside"> -->
-                <page string="Geo Localization" name="geo_localization">
+            <xpath expr="//page[@name='geo_location']" position="inside">
+                <group>
                     <group>
-                        <group>
-                            <separator string="Partner Activation" colspan="2"/>
-                            <field name="grade_id" widget="selection"/>
-                            <field name="activation" widget="selection"/>
-                            <field name="partner_weight"/>
-                        </group>
-                        <group>
-                            <separator string="Partner Review" colspan="2"/>
-                            <field name="date_review"/>
-                            <field name="date_review_next"/>
-                            <field name="date_partnership"/>
-                        </group>
+                        <separator string="Partner Activation" colspan="2"/>
+                        <field name="grade_id" widget="selection"/>
+                        <field name="activation" widget="selection"/>
+                        <field name="partner_weight"/>
+                        <field name="assigned_partner_id"/>
                     </group>
-                    <group colspan="2" col="2">
-                        <separator string="Geo Localization" colspan="2"/>
-                        <button
-                            string="Geo Localize"
-                            name="geo_localize"
-                            colspan="2"
-                            icon="gtk-apply"
-                            type="object"/>
-                        <field name="partner_latitude"/>
-                        <field name="partner_longitude"/>
-                        <field name="date_localization"/>
+                    <group>
+                        <separator string="Partner Review" colspan="2"/>
+                        <field name="date_review"/>
+                        <field name="date_review_next"/>
+                        <field name="date_partnership"/>
                     </group>
-                    <newline/>
-
-                    <field name="opportunity_assigned_ids" colspan="4" nolabel="1">
-                         <tree string="Assigned Opportunities">
-                             <field name="create_date"/>
-                             <field name="name"/>
-                             <field name="type"/>
-                             <field name="probability" invisible="1"/>
-                             <field name="stage_id"/>
-                             <field name="section_id"
-                                    invisible="context.get('invisible_section', True)"
-                                    groups="base.group_multi_salesteams"/>
-                             <field name="user_id" />
-                             <button string="Convert to Opportunity"
-                                    name="convert_opportunity"
-                                    type="object" 
-                                    icon="gtk-convert"
-                                    attrs="{'invisible':[('type','=','opportunity')]}" />
-                             <button name="case_escalate" string="Escalate"
-                                    type="object"
-                                    icon="gtk-go-up" 
-                                    attrs="{'invisible':[('probability', '=', 100)]}" />
-                         </tree>
-                    </field>
-                </page>
+                    <group>
+                        <button name="%(crm.relate_partner_opportunities)d"  string="Assigned Opportunities" type="action" />
+                    </group>
+                </group>
             </xpath>
         </field>
     </record>
index efa22ca..e20d89e 100644 (file)
   I check sale order after added delivery cost.
 -
   !python {model: sale.order.line}: |
+    from openerp.tools import float_compare
     line_ids = self.search(cr, uid, [('order_id','=', ref('sale_normal_delivery_charges')), ('product_id','=', ref('product_product_delivery'))])
     assert len(line_ids), "Delivery cost is not Added"
     line_data = self.browse(cr ,uid ,line_ids[0] ,context)
-    assert line_data.price_subtotal == 10, "Delivey cost is not correspond."
+    assert float_compare(line_data.price_subtotal, 10, precision_digits=2) == 0, "Delivey cost is not correspond."
 -
   I confirm the sale order.
 -
   I check sale order after added delivery cost.
 -
   !python {model: sale.order.line}: |
+    from openerp.tools import float_compare
     line_ids = self.search(cr, uid, [('order_id','=', ref('sale_free_delivery_charges')), ('product_id','=', ref('product_product_delivery'))])
     assert len(line_ids), "Delivery cost is not Added"
     line_data = self.browse(cr ,uid ,line_ids[0] ,context)
-    assert line_data.price_subtotal == 0, "Delivey cost is not correspond."
+    assert float_compare(line_data.price_subtotal, 0, precision_digits=2) == 0, "Delivey cost is not correspond."
 
 -
   I set default delivery policy.
@@ -91,4 +93,4 @@
     {}
 -
   !python {model: sale.config.settings}: |
-    self.execute(cr, uid, [ref('default_delivery_policy')], context=context)
\ No newline at end of file
+    self.execute(cr, uid, [ref('default_delivery_policy')], context=context)
index d653a6d..fc0009f 100644 (file)
@@ -23,3 +23,4 @@ import event
 import wizard
 import report
 import res_partner
+import res_config
index d31f408..b5bcab0 100644 (file)
@@ -47,6 +47,7 @@ Key Features
         'event_data.xml',
         'report/report_event_registration_view.xml',
         'res_partner_view.xml',
+        'res_config_view.xml',
         'email_template.xml',
         'views/event.xml',
     ],
diff --git a/addons/event/res_config.py b/addons/event/res_config.py
new file mode 100644 (file)
index 0000000..215dc34
--- /dev/null
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from openerp.osv import fields, osv
+
+class event_config_settings(osv.TransientModel):
+    _name='marketing.config.settings'
+    _inherit='marketing.config.settings'
+    _columns = {
+        'module_event_sale': fields.boolean(
+            'Sale different type of ticket',
+            help='Install the event_sale module'),
+        'module_website_event_track': fields.boolean(
+            'Organize few days event with track,full agenda,own menu in website'),
+    }
+
diff --git a/addons/event/res_config_view.xml b/addons/event/res_config_view.xml
new file mode 100644 (file)
index 0000000..fecb77c
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+    
+         <record id="view_event_configuration" model="ir.ui.view">
+            <field name="model">marketing.config.settings</field>
+            <field name="inherit_id" ref="marketing.view_marketing_configuration"/>
+            <field name="arch" type="xml">
+                <div name="config_setting" position="inside">                   
+                    <separator string="Event Features"/>
+                    <group>
+                        <label for="id" string="Settings"/>     
+                        <div>                        
+                            <div name="module_event_sale">    
+                                <field name="module_event_sale" class="oe_inline"/>
+                                <label for="module_event_sale"/>
+                            </div>
+                            <div name="module_website_event_track">
+                                <field name="module_website_event_track"/>
+                                <label for="module_website_event_track"/>
+                            </div>
+                        </div>
+                    </group>
+                </div>                  
+            </field>
+        </record>
+    </data>
+</openerp>
+                    
+                 
+       
index ae28ffa..9770207 100644 (file)
@@ -136,7 +136,7 @@ class fleet_vehicle_model(osv.Model):
     _columns = {
         'name': fields.function(_model_name_get_fnc, type="char", string='Name', store=True),
         'modelname': fields.char('Model name', required=True), 
-        'brand_id': fields.many2one('fleet.vehicle.model.brand', 'Model Brand', required=True, help='Brand of the vehicle'),
+        'brand_id': fields.many2one('fleet.vehicle.model.brand', 'Make', required=True, help='Make of the vehicle'),
         'vendors': fields.many2many('res.partner', 'fleet_vehicle_model_vendors', 'model_id', 'partner_id', string='Vendors'),
         'image': fields.related('brand_id', 'image', type="binary", string="Logo"),
         'image_medium': fields.related('brand_id', 'image_medium', type="binary", string="Logo (medium)"),
@@ -160,7 +160,7 @@ class fleet_vehicle_model_brand(osv.Model):
         return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
 
     _columns = {
-        'name': fields.char('Brand Name', required=True),
+        'name': fields.char('Make', required=True),
         'image': fields.binary("Logo",
             help="This field holds the image used as logo for the brand, limited to 1024x1024px."),
         'image_medium': fields.function(_get_image, fnct_inv=_set_image,
index 89faafa..fc754b9 100644 (file)
@@ -62,7 +62,7 @@
                 <search string="Vehicles costs" >
                     <field name="brand_id" />
                     <group expand="1" string="Group By">
-                        <filter name="groupby_brand" context="{'group_by' : 'brand_id'}" string="Brand"/>
+                        <filter name="groupby_brand" context="{'group_by' : 'brand_id'}" string="Make"/>
                     </group>
                 </search>
             </field>
@@ -78,7 +78,7 @@
               <p class="oe_view_nocontent_create">
                 Click to create a new model.
               </p><p>
-                You can define several models (e.g. A3, A4) for each brand (Audi).
+                You can define several models (e.g. A3, A4) for each make (Audi).
               </p>
             </field>
         </record>
@@ -87,7 +87,7 @@
             <field name="name">fleet.vehicle.model.brand.tree</field>
             <field name="model">fleet.vehicle.model.brand</field>
             <field name="arch" type="xml">
-                <tree string="Model Brand">
+                <tree string="Model Make">
                     <field name="name" />
                 </tree>
             </field>
@@ -97,7 +97,7 @@
             <field name="name">fleet.vehicle.model.brand.form</field>
             <field name="model">fleet.vehicle.model.brand</field>
             <field name="arch" type="xml">
-                <form string="Model Brand">
+                <form string="Model Make">
                     <sheet>
                         <group>
                             <div>
         </record>
 
         <record model='ir.actions.act_window' id='fleet_vehicle_model_brand_act'>
-            <field name="name">Model brand of Vehicle</field>
+            <field name="name">Model make of Vehicle</field>
             <field name="res_model">fleet.vehicle.model.brand</field>
             <field name="view_type">form</field>
             <field name="view_mode">kanban,tree,form</field>
             <field name="help" type="html">
               <p class="oe_view_nocontent_create">
-                Click to create a new brand.
+                Click to create a new make.
               </p>
             </field>
         </record>
index 5e081fa..963f338 100644 (file)
@@ -2,7 +2,7 @@
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2004-Today OpenERP S.A. (<http://openerp.com>).
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -94,7 +94,7 @@ class hr_job(osv.Model):
 
     _name = "hr.job"
     _description = "Job Position"
-    _inherit = ['mail.thread', 'ir.needaction_mixin']
+    _inherit = ['mail.thread']
     _columns = {
         'name': fields.char('Job Name', required=True, select=True),
         'expected_employees': fields.function(_get_nbr_employees, string='Total Forecasted Employees',
@@ -368,19 +368,21 @@ class hr_employee(osv.osv):
 
 
 class hr_department(osv.osv):
+    _name = "hr.department"
+    _description = "HR Department"
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
 
     def _dept_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
         res = self.name_get(cr, uid, ids, context=context)
         return dict(res)
 
-    _name = "hr.department"
     _columns = {
         'name': fields.char('Department Name', required=True),
         'complete_name': fields.function(_dept_name_get_fnc, type="char", string='Name'),
         'company_id': fields.many2one('res.company', 'Company', select=True, required=False),
         'parent_id': fields.many2one('hr.department', 'Parent Department', select=True),
         'child_ids': fields.one2many('hr.department', 'parent_id', 'Child Departments'),
-        'manager_id': fields.many2one('hr.employee', 'Manager'),
+        'manager_id': fields.many2one('hr.employee', 'Manager', track_visibility='onchange'),
         'member_ids': fields.one2many('hr.employee', 'department_id', 'Members', readonly=True),
         'jobs_ids': fields.one2many('hr.job', 'department_id', 'Jobs'),
         'note': fields.text('Note'),
@@ -420,13 +422,27 @@ class hr_department(osv.osv):
             res.append((record['id'], name))
         return res
 
-
-class res_users(osv.osv):
-    _name = 'res.users'
-    _inherit = 'res.users'
-    _columns = {
-        'employee_ids': fields.one2many('hr.employee', 'user_id', 'Related employees'),
-    }
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+    def create(self, cr, uid, vals, context=None):
+        # TDE note: auto-subscription of manager done by hand, because currently
+        # the tracking allows to track+subscribe fields linked to a res.user record
+        # An update of the limited behavior should come, but not currently done.
+        manager_id = vals.get("manager_id")
+        new_id = super(hr_department, self).create(cr, uid, vals, context=context)
+        if manager_id:
+            employee = self.pool.get('hr.employee').browse(cr, uid, manager_id, context=context)
+            if employee.user_id:
+                self.message_subscribe_users(cr, uid, [new_id], user_ids=[employee.user_id.id], context=context)
+        return new_id
+
+    def write(self, cr, uid, ids, vals, context=None):
+        # TDE note: auto-subscription of manager done by hand, because currently
+        # the tracking allows to track+subscribe fields linked to a res.user record
+        # An update of the limited behavior should come, but not currently done.
+        if isinstance(ids, (int, long)):
+            ids = [ids]
+        manager_id = vals.get("manager_id")
+        if manager_id:
+            employee = self.pool.get('hr.employee').browse(cr, uid, manager_id, context=context)
+            if employee.user_id:
+                self.message_subscribe_users(cr, uid, [ids], user_ids=[employee.user_id.id], context=context)
+        return super(hr_department, self).write(cr, uid, ids, vals, context=context)
index 382cc4d..d38268f 100644 (file)
@@ -16,6 +16,8 @@ Leave Management (keep track of employee leaves), Expense Management (manage emp
         <record id="employee" model="hr.employee">
             <field name="name">Administrator</field>
             <field name="user_id" ref="base.user_root"/>
+            <field name="address_id" ref="base.partner_root"/>
+            <field name="address_home_id" ref="base.partner_root"/>
             <field name="image" type="base64" file="hr/static/img/employee-image.png"/>
         </record>
 
index a045baa..708387d 100644 (file)
@@ -9,7 +9,7 @@
             sequence="90"/>
         <menuitem id="menu_hr_reporting" parent="base.menu_reporting" name="Human Resources" sequence="40" />
         <menuitem id="menu_hr_main" parent="menu_hr_root" name="Human Resources" sequence="0"/>
-        <menuitem id="menu_hr_configuration" name="Configuration" parent="hr.menu_hr_root" groups="base.group_hr_manager" sequence="50"/>
+        <menuitem id="menu_hr_configuration" name="Configuration" parent="hr.menu_hr_root" groups="base.group_hr_user" sequence="50"/>
         <menuitem id="menu_hr_reporting_timesheet" name="Reports"
             parent="menu_hr_reporting" sequence="6"/>
 
@@ -93,7 +93,7 @@
                         </notebook>
                     </sheet>
                     <div class="oe_chatter">
-                        <field name="message_follower_ids" widget="mail_followers"/>
+                        <field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
                         <field name="message_ids" widget="mail_thread"/>
                     </div>
                 </form>
             <field name="name">hr.employee.tree</field>
             <field name="model">hr.employee</field>
             <field name="arch" type="xml">
-                <tree string="Employees">
+                <tree string="Employees" fonts="bold:message_unread==True">
                     <field name="name"/>
                     <field name="work_phone"/>
                     <field name="work_email"/>
                     <field name="job_id"/>
                     <field name="parent_id"/>
                     <field name="coach_id" invisible="1"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
                     <field name="name" string="Employees" filter_domain="['|',('work_email','ilike',self),('name','ilike',self)]"/>
                     <field name="department_id" />
                     <field name="category_ids" groups="base.group_hr_user"/>
+                    <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <group expand="0" string="Group By">
                         <filter string="Manager" icon="terp-personal" domain="[]" context="{'group_by':'parent_id'}"/>
                         <filter string="Coach" icon="terp-personal" domain="[]" context="{'group_by':'coach_id'}"/>
                 <field name="message_is_follower"/>
                 <field name="message_follower_ids"/>
                 <field name="message_ids"/>
+                <field name="message_summary"/>
+                <field name="message_unread"/>
                 <templates>
                     <t t-name="kanban-box">
                         <div class="oe_employee_vignette">
                                     <li t-if="record.work_email.raw_value"><a t-attf-href="mailto:#{record.work_email.value}"><field name="work_email"/></a></li>
                                 </ul>
                                 <div class="oe_kanban_footer_left">
+                                    <t t-raw="record.message_summary.raw_value"/>
                                     <span title='Messages'><span class='oe_e'>9</span><t t-esc="record.message_ids.raw_value.length"/></span>
                                     <span title='Followers'><span class='oe_e'>+</span><t t-esc="record.message_follower_ids.raw_value.length"/></span>
+                                    
                                 </div>
                                 <div class="oe_followers" groups="base.group_user">
                                     <button t-if="record.message_is_follower.raw_value" name="action_unfollow" type="object" class="oe_follower oe_following">
         </record>
 
         <menuitem action="open_view_categ_form" id="menu_view_employee_category_form"
-            parent="hr.menu_hr_configuration" sequence="1" groups="base.group_no_one"/>
+            parent="hr.menu_hr_configuration" sequence="1" groups="base.group_no_one,base.group_hr_manager"/>
 
         <record id="hr_employee_normal_action_tree" model="ir.actions.act_window">
             <field name="name">Employees</field>
             <field name="name">hr.job.tree</field>
             <field name="model">hr.job</field>
             <field name="arch" type="xml">
-                <tree string="Job">
+                <tree string="Job" fonts="bold:message_unread==True">
                     <field name="name"/>
                     <field name="department_id"/>
                     <field name="no_of_employee"/>
                     <field name="expected_employees"/>
                     <field name="no_of_hired_employee"/>
                     <field name="state"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
                     <filter domain="[('state','=','open')]" string="In Position"/>
                     <filter domain="[('state','=','recruit')]" string="In Recruitment" name="in_recruitment"/>
                     <field name="department_id"/>
+                    <separator/>
+                    <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <group expand="0" string="Group By">
                         <filter string="Department" domain="[]" context="{'group_by':'department_id'}"/>
                         <filter string="Status" domain="[]" context="{'group_by':'state'}"/>
                             <field name="company_id" widget="selection" groups="base.group_multi_company"/>
                         </group>
                     </sheet>
+                    <div class="oe_chatter">
+                        <field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
+                        <field name="message_ids" widget="mail_thread"/>
+                    </div>
                 </form>
             </field>
         </record>
                 <search string="Departments">
                     <field name="name" string="Department"/>
                     <field name="manager_id" />
+                    <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
                 </search>
              </field>
         </record>
               </p>
             </field>
         </record>
-       <menuitem action="open_module_tree_department" id="menu_hr_department_tree" parent="hr.menu_hr_configuration" sequence="5"/>
+        <menuitem action="open_module_tree_department" id="menu_hr_department_tree" parent="hr.menu_hr_configuration" sequence="5" groups="base.group_hr_manager,base.group_hr_user"/>
 
     </data>
 </openerp>
index e6f2ca3..eff3b8b 100644 (file)
@@ -4,13 +4,15 @@ from openerp.osv import fields, osv
 
 class res_users(osv.Model):
     """ Update of res.users class
-        - if adding groups to an user, check if base.group_user is in it
-        (member of 'Employee'), create an employee form linked to it.
-    """
+
+     - add field for the related employee of the user
+     - if adding groups to an user, check if base.group_user is in it (member of
+       'Employee'), create an employee form linked to it. """
     _name = 'res.users'
     _inherit = ['res.users']
 
     _columns = {
+        'employee_ids': fields.one2many('hr.employee', 'user_id', 'Related employees'),
         'display_employees_suggestions': fields.boolean("Display Employees Suggestions"),
     }
 
index 5075cc1..1a5d010 100644 (file)
         </record>
 
         <menuitem
-            sequence="35" id="hr.menu_open_view_attendance_reason_new_config" parent="hr.menu_hr_configuration" groups="base.group_hr_attendance" name="Attendance"/>
+            sequence="35" id="hr.menu_open_view_attendance_reason_new_config" parent="hr.menu_hr_configuration" groups="base.group_hr_attendance,base.group_hr_manager" name="Attendance"/>
         <menuitem action="open_view_attendance_reason" id="menu_open_view_attendance_reason" parent="hr.menu_open_view_attendance_reason_new_config" groups="base.group_hr_attendance"/>
 
         <record id="hr_attendance_employee" model="ir.ui.view">
index 8b58c3e..593faf3 100644 (file)
@@ -2,7 +2,7 @@
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2004-Today OpenERP S.A. (<http://www.openerp.com>).
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -18,6 +18,7 @@
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 ##############################################################################
+
 import time
 
 from openerp.osv import fields, osv
@@ -31,7 +32,7 @@ class hr_employee(osv.osv):
         res = {}
         obj_contract = self.pool.get('hr.contract')
         for emp in self.browse(cr, uid, ids, context=context):
-            contract_ids = obj_contract.search(cr, uid, [('employee_id','=',emp.id),], order='date_start', context=context)
+            contract_ids = obj_contract.search(cr, uid, [('employee_id', '=', emp.id)], order='date_start', context=context)
             if contract_ids:
                 res[emp.id] = contract_ids[-1:][0]
             else:
@@ -65,26 +66,40 @@ class hr_contract_type(osv.osv):
         'name': fields.char('Contract Type', required=True),
     }
 
+
 class hr_contract(osv.osv):
     _name = 'hr.contract'
     _description = 'Contract'
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
+
+    _track = {
+        'state': {
+            'hr_contract.mt_contract_pending': lambda self, cr, uid, obj, ctx=None: obj.state == 'pending',
+            'hr_contract.mt_contract_close': lambda self, cr, uid, obj, ctx=None: obj.state == 'close',
+        },
+    }
+
     _columns = {
         'name': fields.char('Contract Reference', required=True),
         'employee_id': fields.many2one('hr.employee', "Employee", required=True),
-        'department_id': fields.related('employee_id','department_id', type='many2one', relation='hr.department', string="Department", readonly=True),
+        'department_id': fields.many2one('hr.department', string="Department"),
         'type_id': fields.many2one('hr.contract.type', "Contract Type", required=True),
         'job_id': fields.many2one('hr.job', 'Job Title'),
         'date_start': fields.date('Start Date', required=True),
         'date_end': fields.date('End Date'),
         'trial_date_start': fields.date('Trial Start Date'),
         'trial_date_end': fields.date('Trial End Date'),
-        'working_hours': fields.many2one('resource.calendar','Working Schedule'),
-        'wage': fields.float('Wage', digits=(16,2), required=True, help="Basic Salary of the employee"),
+        'working_hours': fields.many2one('resource.calendar', 'Working Schedule'),
+        'wage': fields.float('Wage', digits=(16, 2), required=True, help="Basic Salary of the employee"),
         'advantages': fields.text('Advantages'),
         'notes': fields.text('Notes'),
         'permit_no': fields.char('Work Permit No', required=False, readonly=False),
         'visa_no': fields.char('Visa No', required=False, readonly=False),
         'visa_expire': fields.date('Visa Expire Date'),
+        'state': fields.selection(
+            [('draft', 'New'), ('open', 'Running'), ('pending', 'To Renew'), ('close', 'Expired')],
+            string='Status', track_visibility='onchange',
+            help='Status of the contract'),
     }
 
     def _get_type(self, cr, uid, context=None):
@@ -93,26 +108,33 @@ class hr_contract(osv.osv):
 
     _defaults = {
         'date_start': lambda *a: time.strftime("%Y-%m-%d"),
-        'type_id': _get_type
+        'type_id': _get_type,
+        'state': 'draft',
     }
 
     def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
         if not employee_id:
-            return {'value': {'job_id': False}}
+            return {'value': {'job_id': False, 'department_id': False}}
         emp_obj = self.pool.get('hr.employee').browse(cr, uid, employee_id, context=context)
-        job_id = False
+        job_id = dept_id = False
         if emp_obj.job_id:
             job_id = emp_obj.job_id.id
-        return {'value': {'job_id': job_id}}
+        if emp_obj.department_id:
+            dept_id = emp_obj.department_id.id
+        return {'value': {'job_id': job_id, 'department_id': dept_id}}
 
     def _check_dates(self, cr, uid, ids, context=None):
         for contract in self.read(cr, uid, ids, ['date_start', 'date_end'], context=context):
-             if contract['date_start'] and contract['date_end'] and contract['date_start'] > contract['date_end']:
-                 return False
+            if contract['date_start'] and contract['date_end'] and contract['date_start'] > contract['date_end']:
+                return False
         return True
 
     _constraints = [
         (_check_dates, 'Error! Contract start-date must be less than contract end-date.', ['date_start', 'date_end'])
     ]
 
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+    def set_as_pending(self, cr, uid, ids, context=None):
+        return self.write(cr, uid, ids, {'state': 'pending'}, context=context)
+
+    def set_as_close(self, cr, uid, ids, context=None):
+        return self.write(cr, uid, ids, {'state': 'close'}, context=context)
index 9720091..bdb344f 100644 (file)
             <field name="name">Subcontractor</field>
         </record>
 
+        <!-- Contract-related subtypes for messaging / Chatter -->
+        <record id="mt_contract_pending" model="mail.message.subtype">
+            <field name="name">To Renew</field>
+            <field name="res_model">hr.contract</field>
+            <field name="default" eval="True"/>
+            <field name="description">Contract about to expire</field>
+        </record>
+        <record id="mt_contract_close" model="mail.message.subtype">
+            <field name="name">Expired</field>
+            <field name="res_model">hr.contract</field>
+            <field name="default" eval="False"/>
+            <field name="description">Contract expired</field>
+        </record>
+        <!-- Department-related (parent) subtypes for messaging / Chatter -->
+        <record id="mt_department_contract_pending" model="mail.message.subtype">
+            <field name="name">Contract to Renew</field>
+            <field name="res_model">hr.department</field>
+            <field name="default" eval="False"/>
+            <field name="parent_id" eval="ref('mt_contract_pending')"/>
+            <field name="relation_field">department_id</field>
+            <field name="description">Contract about to expire</field>
+        </record>
+
+        <!-- base action rule about "Expiring Soon" contracts -->
+        <record id="contract_open" model="ir.filters">
+            <field name="name">Open Contracts</field>
+            <field name="model_id">hr.contract</field>
+            <field name="domain">[('state', '=', 'open')]</field>
+            <field name="user_id" eval="False"/>
+        </record>
+        <record id="contract_set_as_pending" model="ir.actions.server">
+            <field name="name">HR Contract: set as pending</field>
+            <field name="model_id" ref="model_hr_contract"/>
+            <field name="condition">True</field>
+            <field name="type">ir.actions.server</field>
+            <field name="state">code</field>
+            <field name="code">object.set_as_pending()</field>
+        </record>
+        <record id="rule_contract_1_set_as_pending" model="base.action.rule">
+            <field name="name">HR Contract: check for pending</field>
+            <field name="model_id" ref="model_hr_contract"/>
+            <field name="sequence">50</field>
+            <field name="kind">on_time</field>
+            <field name="filter_id" ref="contract_open"/>
+            <field name="trg_date_id" ref="hr_contract.field_hr_contract_date_end"/>
+            <field name="trg_date_range">-7</field>
+            <field name="trg_date_range_type">day</field>
+            <field name="server_action_ids" eval="[(6, 0, [ref('contract_set_as_pending')])]"/>
+        </record>
+         <record id="rule_contract_2_set_as_pending" model="base.action.rule">
+            <field name="name">HR Contract: check for pending</field>
+            <field name="model_id" ref="model_hr_contract"/>
+            <field name="sequence">51</field>
+            <field name="kind">on_time</field>
+            <field name="filter_id" ref="contract_open"/>
+            <field name="trg_date_id" ref="hr_contract.field_hr_contract_visa_expire"/>
+            <field name="trg_date_range">-60</field>
+            <field name="trg_date_range_type">day</field>
+            <field name="server_action_ids" eval="[(6, 0, [ref('contract_set_as_pending')])]"/>
+        </record>
+        <!-- base action rule about "Expired" contracts -->
+        <record id="contract_set_as_close" model="ir.actions.server">
+            <field name="name">HR Contract: set as close</field>
+            <field name="model_id" ref="model_hr_contract"/>
+            <field name="condition">True</field>
+            <field name="type">ir.actions.server</field>
+            <field name="state">code</field>
+            <field name="code">object.set_as_close()</field>
+        </record>
+        <record id="rule_contract_3_set_as_close" model="base.action.rule">
+            <field name="name">HR Contract: check for close</field>
+            <field name="model_id" ref="model_hr_contract"/>
+            <field name="sequence">52</field>
+            <field name="kind">on_time</field>
+            <field name="trg_date_id" ref="hr_contract.field_hr_contract_date_end"/>
+            <field name="trg_date_range">1</field>
+            <field name="trg_date_range_type">day</field>
+            <field name="server_action_ids" eval="[(6, 0, [ref('contract_set_as_close')])]"/>
+        </record>
+        <record id="rule_contract_4_set_as_close" model="base.action.rule">
+            <field name="name">HR Contract: check for close</field>
+            <field name="model_id" ref="model_hr_contract"/>
+            <field name="sequence">53</field>
+            <field name="kind">on_time</field>
+            <field name="trg_date_id" ref="hr_contract.field_hr_contract_visa_expire"/>
+            <field name="trg_date_range">1</field>
+            <field name="trg_date_range_type">day</field>
+            <field name="server_action_ids" eval="[(6, 0, [ref('contract_set_as_close')])]"/>
+        </record>
+
     </data>
 </openerp>
index 3c06c47..f0e1510 100644 (file)
@@ -13,7 +13,7 @@
             <field name="context">{'search_default_employee_id': [active_id], 'default_employee_id': active_id}</field>
         </record>
         
-        <menuitem id="next_id_56" name="Contract" parent="hr.menu_hr_configuration" sequence="30" groups="base.group_no_one"/>
+        <menuitem id="next_id_56" name="Contract" parent="hr.menu_hr_configuration" sequence="30" groups="base.group_no_one,base.group_hr_manager"/>
         <record id="hr_hr_employee_view_form2" model="ir.ui.view">
             <field name="name">hr.hr.employee.view.form2</field>
             <field name="model">hr.employee</field>
             <field name="model">hr.contract</field>
             <field name="arch" type="xml">
                 <search string="Search Contract">
-                   <field name="name" string="Contracts"/>
-                   <field name="date_start"/>
-                   <field name="date_end"/>
-                   <field name="working_hours"/>
-                   <field name="employee_id"/>
-                   <group expand="0" string="Group By">
-                       <filter string="Employee" icon="terp-personal" domain="[]" context="{'group_by':'employee_id'}"/>
-                       <filter string="Working Schedule" icon="terp-go-week" domain="[]" context="{'group_by':'working_hours'}"/>
-                       <filter string="Job" icon="terp-gtk-select-all" domain="[]" context="{'group_by':'job_id'}"/>
-                       <filter string="Contract Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'type_id'}"/>
-                   </group>
-               </search>
+                    <field name="name" string="Contracts"/>
+                    <field name="date_start"/>
+                    <field name="date_end"/>
+                    <field name="working_hours"/>
+                    <field name="employee_id"/>
+                    <field name="department_id"/>
+                    <field name="state"/>
+                    <filter string="To Renew" name="to_renew" domain="[('state', '=', 'pending')]"/>
+                    <separator />
+                    <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
+                    <group expand="0" string="Group By">
+                        <filter string="Employee" domain="[]" context="{'group_by':'employee_id'}"/>
+                        <filter string="Job" domain="[]" context="{'group_by':'job_id'}"/>
+                        <filter string="Contract Type" domain="[]" context="{'group_by':'type_id'}"/>
+                    </group>
+                </search>
             </field>
         </record>
 
@@ -77,6 +81,9 @@
             <field name="model">hr.contract</field>
             <field name="arch" type="xml">
                 <form string="Contract">
+                    <header>
+                        <field name="state" widget="statusbar" clickable="1"/>
+                    </header>
                     <sheet>
                     <div class="oe_title">
                         <label for="name" class="oe_edit_only"/>
@@ -90,6 +97,7 @@
                             <field name="job_id"/>
                         </group>
                         <group>
+                            <field name="department_id"/>
                             <field name="type_id"/>
                         </group>
                     </group>
                         </page>
                     </notebook>
                     </sheet>
+                    <div class="oe_chatter">
+                        <field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
+                        <field name="message_ids" widget="mail_thread"/>
+                    </div>
                 </form>
             </field>
         </record>
             <field name="name">hr.contract.view.tree</field>
             <field name="model">hr.contract</field>
             <field name="arch" type="xml">
-                <tree string="Contracts">
+                <tree string="Contracts" fonts="bold:message_unread == True">
                     <field name="name"/>
                     <field name="employee_id"/>
                     <field name="type_id"/>
                     <field name="date_start"/>
                     <field name="date_end"/>
                     <field name="wage" invisible="1"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
index 068af5b..dc35b28 100644 (file)
@@ -2,7 +2,7 @@
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2004-Today OpenERP S.A. (<http://www.openerp.com>).
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -135,7 +135,7 @@ class hr_employee(osv.Model):
 
 class hr_evaluation(osv.Model):
     _name = "hr_evaluation.evaluation"
-    _inherit = "mail.thread"
+    _inherit = ['mail.thread']
     _description = "Employee Appraisal"
     _columns = {
         'date': fields.date("Appraisal Deadline", required=True, select=True),
@@ -157,7 +157,7 @@ class hr_evaluation(osv.Model):
             ('wait', 'Plan In Progress'),
             ('progress', 'Waiting Appreciation'),
             ('done', 'Done'),
-        ], 'Status', required=True, readonly=True, copy=False),
+        ], 'Status', required=True, readonly=True, track_visibility='onchange', copy=False),
         'date_close': fields.date('Ending Date', select=True),
     }
     _defaults = {
@@ -270,7 +270,7 @@ class hr_evaluation(osv.Model):
 
 class hr_evaluation_interview(osv.Model):
     _name = 'hr.evaluation.interview'
-    _inherit = 'mail.thread'
+    _inherit = ['mail.thread']
     _rec_name = 'user_to_review_id'
     _description = 'Appraisal Interview'
     _columns = {
index 38171b0..fb3fad5 100644 (file)
@@ -57,6 +57,5 @@
            <field eval="'run_employee_evaluation'" name="function" />
            <field eval="'(False,)'" name="args" />
         </record>
-     </data>
-
+    </data>
 </openerp>
index 7ec83cd..5f1b2db 100644 (file)
@@ -72,9 +72,9 @@
         </record>
 
         <menuitem name="Appraisal" parent="hr.menu_hr_root" id="menu_eval_hr" sequence="25"/>
-        <menuitem name="Periodic Appraisal" parent="hr.menu_hr_configuration" id="menu_eval_hr_config" sequence="4"/>
+        <menuitem name="Periodic Appraisal" parent="hr.menu_hr_configuration" id="menu_eval_hr_config" sequence="4" groups="base.group_hr_manager"/>
         <menuitem parent="hr.menu_hr_configuration" id="menu_open_view_hr_evaluation_plan_tree"
-            action="open_view_hr_evaluation_plan_tree" sequence="15"/>
+            action="open_view_hr_evaluation_plan_tree" sequence="15" groups="base.group_hr_manager"/>
 
         <record model="ir.ui.view" id="view_hr_evaluation_plan_phase_form">
             <field name="name">hr_evaluation.plan.phase.form</field>
             <field name="name">hr_evaluation.evaluation.tree</field>
             <field name="model">hr_evaluation.evaluation</field>
             <field name="arch" type="xml">
-                <tree colors="blue:state == 'draft';black:state in ('wait','progress');gray:state in('done','cancel')" string="Appraisal">
+                <tree colors="blue:state == 'draft';black:state in ('wait','progress');gray:state in('done','cancel')" string="Appraisal"
+                        fonts="bold: message_unread == True">
                     <field name="employee_id"/>
                     <field name="plan_id"/>
                     <field name="date"/>
                     <field name="rating"/>
                     <field name="state"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
             <field name="arch" type="xml">
                 <search string="Search Appraisal">
                     <field name="date"/>
+                    <field name="employee_id"/>
+                    <field name="plan_id"/>
                     <filter icon="terp-check" string="Pending" domain="[('state','=','wait')]" help="Appraisal that are in Plan In Progress state"/>
                     <filter icon="terp-camera_test" string="In progress" domain="[('state','=','progress')]" help="Appraisal that are in waiting appreciation state"/>
-                    <field name="employee_id" />
-                    <field name="plan_id"/>
+                    <separator/>
+                    <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <group expand='0' string='Group by...'>
                         <filter string='Employee' icon="terp-personal" domain="[]" context="{'group_by' : 'employee_id'}" />
                         <filter string='Plan' icon="terp-stock_align_left_24" domain="[]" context="{'group_by' : 'plan_id'}" />
             <field name="name">hr_evaluation.interview.tree</field>
             <field name="model">hr.evaluation.interview</field>
             <field name="arch" type="xml">
-                <tree string="Interview Appraisal">
+                <tree string="Interview Appraisal" fonts="bold: message_unread == True">
                     <field name="deadline" string="Deadline Date"/>
                     <field name="survey_id" domain="[('res_model','=','hr_evaluation')]"/>
                     <field name="user_id" string="Interviewer"/>
                     <button name="action_print_survey" string="Print Survey" type="object" icon="gtk-print"  attrs="{'readonly':[('survey_id','=',False)]}"/>
                     <button name="%(mail.action_email_compose_message_wizard)d" string="Send Reminder Email" icon="terp-mail-message-new" type="action" states="waiting_answer"/>
                     <field name="state"/>
+                    <field name="message_unread" invisible="1"/>
                     <button string="Send Request" name="survey_req_waiting_answer" states="draft" type="object" icon="gtk-yes" />
                     <button string="Done" name="survey_req_done" states="waiting_answer" type="object" icon="gtk-jump-to" />
                 </tree>
             <field name="model">hr.evaluation.interview</field>
             <field name="arch" type="xml">
                 <search string="Search Appraisal">
-                    <field name="deadline"/>
-                    <filter icon="terp-gtk-go-back-rtl" string="To Do" name="todo" domain="[('state','=','waiting_answer')]"/>
                     <field name="user_to_review_id"/>
                     <field name="user_id" string="Interviewer"/>
+                    <field name="deadline"/>
+                    <filter icon="terp-gtk-go-back-rtl" string="To Do" name="todo" domain="[('state','=','waiting_answer')]"/>
+                    <separator/>
+                    <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <group expand="0" string="Group By">
                         <filter string="Interviewer" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
                         <filter string="Survey" icon="terp-stock_align_left_24" domain="[]" context="{'group_by':'survey_id'}"/>
index eb8a096..1cdfc5e 100644 (file)
@@ -53,7 +53,7 @@ class hr_expense_expense(osv.osv):
         return user.company_id.currency_id.id
 
     _name = "hr.expense.expense"
-    _inherit = ['mail.thread']
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
     _description = "Expense"
     _order = "id desc"
     _track = {
@@ -69,6 +69,7 @@ class hr_expense_expense(osv.osv):
         'id': fields.integer('Sheet ID', readonly=True),
         'date': fields.date('Date', select=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
         'journal_id': fields.many2one('account.journal', 'Force Journal', help = "The journal used when the expense is done."),
+        'employee_payable_account_id': fields.many2one('account.account', 'Employee Account', help="Employee payable account"),
         'employee_id': fields.many2one('hr.employee', "Employee", required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
         'user_id': fields.many2one('res.users', 'User', required=True),
         'date_confirm': fields.date('Confirmation Date', select=True, copy=False,
@@ -77,7 +78,7 @@ class hr_expense_expense(osv.osv):
                                   help="Date of the acceptation of the sheet expense. It's filled when the button Accept is pressed."),
         'user_valid': fields.many2one('res.users', 'Validation By', readonly=True, copy=False,
                                       states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
-        'account_move_id': fields.many2one('account.move', 'Ledger Posting', copy=False),
+        'account_move_id': fields.many2one('account.move', 'Ledger Posting', copy=False, track_visibility="onchange"),
         'line_ids': fields.one2many('hr.expense.line', 'expense_id', 'Expense Lines', copy=True,
                                     readonly=True, states={'draft':[('readonly',False)]} ),
         'note': fields.text('Note'),
@@ -126,15 +127,20 @@ class hr_expense_expense(osv.osv):
     def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
         emp_obj = self.pool.get('hr.employee')
         department_id = False
+        employee_payable_account_id = False
         company_id = False
         if employee_id:
             employee = emp_obj.browse(cr, uid, employee_id, context=context)
             department_id = employee.department_id.id
             company_id = employee.company_id.id
-        return {'value': {'department_id': department_id, 'company_id': company_id}}
+            if employee.address_home_id and employee.address_home_id.property_account_payable:
+                employee_payable_account_id = employee.address_home_id.property_account_payable.id
+        return {'value': {'department_id': department_id, 'company_id': company_id, 'employee_payable_account_id': employee_payable_account_id}}
 
     def expense_confirm(self, cr, uid, ids, context=None):
         for expense in self.browse(cr, uid, ids):
+            if not expense.line_ids:
+                raise osv.except_osv(_('Error!'),_('You cannot submit expense which has no expense line.'))
             if expense.employee_id and expense.employee_id.parent_id.user_id:
                 self.message_subscribe_users(cr, uid, [expense.id], user_ids=[expense.employee_id.parent_id.user_id.id])
         return self.write(cr, uid, ids, {'state': 'confirm', 'date_confirm': time.strftime('%Y-%m-%d')}, context=context)
@@ -226,22 +232,22 @@ class hr_expense_expense(osv.osv):
         '''
         move_obj = self.pool.get('account.move')
         for exp in self.browse(cr, uid, ids, context=context):
-            if not exp.employee_id.address_home_id:
-                raise osv.except_osv(_('Error!'), _('The employee must have a home address.'))
-            if not exp.employee_id.address_home_id.property_account_payable.id:
-                raise osv.except_osv(_('Error!'), _('The employee must have a payable account set on his home address.'))
+            if not exp.employee_payable_account_id:
+                raise osv.except_osv(_('Error!'), _('No employee account payable found for the expense '))
+
             company_currency = exp.company_id.currency_id.id
-            diff_currency_p = exp.currency_id.id <> company_currency
-            
+            diff_currency_p = exp.currency_id.id != company_currency
+
             #create the move that will contain the accounting entries
             move_id = move_obj.create(cr, uid, self.account_move_get(cr, uid, exp.id, context=context), context=context)
-        
+
             #one account.move.line per expense line (+taxes..)
             eml = self.move_line_get(cr, uid, exp.id, context=context)
-            
+
             #create one more move line, a counterline for the total on payable account
             total, total_currency, eml = self.compute_expense_totals(cr, uid, exp, company_currency, exp.name, eml, context=context)
-            acc = exp.employee_id.address_home_id.property_account_payable.id
+
+            acc = exp.employee_payable_account_id.id or False
             eml.append({
                     'type': 'dest',
                     'name': '/',
@@ -254,7 +260,7 @@ class hr_expense_expense(osv.osv):
                     })
 
             #convert eml into an osv-valid format
-            lines = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.employee_id.address_home_id, exp.date_confirm, context=context)), eml)
+            lines = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.employee_id.company_id.partner_id, exp.date_confirm, context=context)), eml)
             journal_id = move_obj.browse(cr, uid, move_id, context).journal_id
             # post the journal entry if 'Skip 'Draft' State for Manual Entries' is checked
             if journal_id.entry_posted:
index ddb6bdd..ac5df67 100644 (file)
         <record id="mt_expense_approved" model="mail.message.subtype">
             <field name="name">Approved</field>
             <field name="res_model">hr.expense.expense</field>
+            <field name="default" eval="False"/>
             <field name="description">Expense approved</field>
         </record>
         <record id="mt_expense_refused" model="mail.message.subtype">
             <field name="name">Refused</field>
             <field name="res_model">hr.expense.expense</field>
+            <field name="default" eval="False"/>
             <field name="description">Expense refused</field>
         </record>
         <record id="mt_expense_confirmed" model="mail.message.subtype">
             <field name="name">To Approve</field>
             <field name="res_model">hr.expense.expense</field>
             <field name="description">Expense confirmed, waiting confirmation</field>
+            <field name="default" eval="True"/>
+        </record>
+        <!-- Department (Parent) related subtypes for messaging / Chatter -->
+        <record id="mt_department_expense_confirmed" model="mail.message.subtype">
+            <field name="name">Expenses To Approve</field>
+            <field name="res_model">hr.department</field>
+            <field name="default" eval="False"/>
+            <field name="parent_id" eval="ref('mt_expense_confirmed')"/>
+            <field name="relation_field">department_id</field>
+            <field name="sequence" eval="10"/>
         </record>
 
     </data>
index 8e91312..2d72636 100644 (file)
             <field name="name">hr.expense.expense.tree</field>
             <field name="model">hr.expense.expense</field>
             <field name="arch" type="xml">
-                <tree string="Expenses" colors="blue:state=='draft'">
+                <tree string="Expenses" colors="blue:state=='draft'" fonts="bold: message_unread == True">
                     <field name="employee_id"/>
                     <field name="department_id" invisible="1"/>
                     <field name="date"/>
                     <field name="user_id" invisible="1"/>
-                    <field name="name"/>
+                    <field name="name" string="Expense Sheet"/>
                     <field name="currency_id" groups="base.group_multi_currency"/>
                     <field name="amount" sum="Total Amount"/>
                     <field name="state"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
@@ -46,7 +47,7 @@
                     <field name="employee_id"/>
                     <field name="date"/>
                     <field name="department_id"/>
-                    <field name="name"/>
+                    <field name="name" string="Expense Sheet"/>
                     <field name="amount"/>
                     <field name="state"/>
                     <button name="confirm" states="draft" string="Confirm" type="workflow" icon="gtk-apply"/>
                             <field name="company_id" groups="base.group_multi_company"/>
                         </group>
                         <group>
-                            <field name="name"/>
+                            <field name="name" string="Expense Sheet" placeholder="e.g. Business travel at Chicago"/>
                             <field name="user_valid" attrs="{'invisible': [('state','=','draft')]}" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'base.group_hr_user']}"/>
                             <field name="currency_id" groups="base.group_multi_currency" on_change="onchange_currency_id(currency_id, company_id)"/>
                         </group>
+                        <group>
+                            <field name="journal_id" widget="selection" attrs="{'readonly':[('state','=','done')]}" domain="[('type', '=', 'purchase')]" string="Journal" groups="account.group_account_user"/>
+                            <field name="employee_payable_account_id" widget="selection" attrs="{'readonly':[('state','=','done')]}" domain="[('type', '=', 'payable')]" groups="account.group_account_user"/>
+                        </group>
+                        <group>
+                            <label class="oe_grey" groups="account.group_account_user" string="If this field is empty, entries will be generated in the purchase journal."/>
+                        </group>
                     </group>
                     <notebook>
-                        <page string="Description">
+                        <page string="Expense Lines">
                             <field name="line_ids" context="{'currency_id': currency_id, 'default_analytic_account': context.get('analytic_account', '')}">
                                 <form string="Expense Lines">
                                     <group>
                                     <field name="sequence" invisible="1"/>
                                     <field name="product_id" on_change="onchange_product_id(product_id, context)" context="{'default_hr_expense_ok':1}"/>
                                     <field name="date_value" string="Expense Date"/>
-                                    <field name="name"/>
+                                    <field name="name" string="Description"/>
                                     <field name="ref"/>
                                     <field domain="[('type','in',['normal','contract'])]" name="analytic_account" groups="analytic.group_analytic_accounting"/>
                                     <field name="uom_id" on_change="onchange_uom(product_id, uom_id, context)"/>
                             </field>
                             <group>
                                 <div>
-                                    <separator string="Notes"/>
+                                    <separator string="Description"/>
                                     <field name="note" placeholder="Free Notes"/>
                                 </div>
                                 <group class="oe_subtotal_footer oe_right">
                                 </group>
                             </group>
                         </page>
-                        <page string="Accounting" groups="account.group_account_user">
-                            <group>
-                                <group string="Accounting Data">
-                                    <field name="journal_id" widget="selection" domain="[('type', '=', 'purchase')]"/>
-                                    <field name="account_move_id"/>
-                                </group>
-                            </group>
-                        </page>
                     </notebook>
                 </sheet>
                 <div class="oe_chatter">
                 <search string="Expense">
                     <field name="name" string="Expenses"/>
                     <field name="date"/>
-                    <filter icon="terp-document-new" domain="[('state','=','draft')]" string="New" help="New Expense"/>
-                    <filter icon="terp-camera_test" domain="[('state','=','confirm')]" string="To Approve" help="Confirmed Expenses"/>
-                    <filter icon="terp-dolar" domain="['|',('state','=','accepted'),('state','=','done')]" string="To Pay" help="Expenses to Invoice"/>
-                    <separator/>
-                    <filter domain="[('user_id', '=', uid)]" string="My Expenses"/>
                     <field name="employee_id"/>
                     <field name="department_id" string="Department" context="{'invisible_department': False}"/>
+                    <filter domain="[('state','=','draft')]" string="New" help="New Expense"/>
+                    <filter domain="[('state','=','confirm')]" string="To Approve" name="confirm" help="Confirmed Expenses"/>
+                    <filter domain="[('state','=','accepted')]" string="To Pay" name="approved" help="Expenses to Invoice"/>
+                    <separator />
+                    <filter domain="[('user_id', '=', uid)]" string="My Expenses"/>
+                    <separator />
+                    <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <group expand="0" string="Group By">
-                        <filter string="Employee" icon="terp-personal" domain="[]" context="{'group_by':'employee_id'}"/>
-                        <filter string="Department" icon="terp-personal+" domain="[]" context="{'group_by':'department_id'}"/>
-                        <filter string="Expenses Month" icon="terp-go-month" domain="[]" context="{'group_by':'date'}" help="Expenses by Month"/>
+                        <filter string="Employee" domain="[]" context="{'group_by':'employee_id'}"/>
+                        <filter string="Department" domain="[]" context="{'group_by':'department_id'}"/>
+                        <filter string="Expenses Month" domain="[]" context="{'group_by':'date'}" help="Expenses by Month"/>
                     </group>
                 </search>
             </field>
             </field>
         </record>
 
-
         <record id="view_product_hr_expense_form" model="ir.ui.view">
             <field name="name">product.template.expense.form</field>
             <field name="model">product.template</field>
             </field>
         </record>
 
-        <menuitem id="menu_hr_product" name="Expense Categories" parent="hr.menu_hr_configuration" action="hr_expense_product"/>
+        <record id="action_approved_expense" model="ir.actions.act_window">
+            <field name="name">Expenses</field>
+            <field name="res_model">hr.expense.expense</field>
+            <field name="view_type">form</field>
+            <field name="view_mode">tree,form</field>
+            <field name="view_id" ref="view_expenses_tree"/>
+            <field name="domain">[('state','=','accepted')]</field> 
+        </record>
+        
+        <record id="action_request_approve_expense" model="ir.actions.act_window">
+            <field name="name">Expenses to Approve</field>
+            <field name="res_model">hr.expense.expense</field>
+            <field name="view_type">form</field>
+            <field name="context">{'search_default_confirm':1, 'needaction_menu_ref': 'hr_expense.menu_expense_all'}</field>
+            <field name="search_view_id" ref="view_hr_expense_filter"/>
+            <field name="view_id" ref="view_expenses_tree"/>
+            <field name="help" type="html">
+              <p class="oe_view_nocontent_create">
+                Click to register new expenses. 
+              </p><p>
+                OpenERP will ensure the whole process is followed; the expense
+                sheet is validated by manager(s), the employee is reimbursed
+                from his expenses, some expenses must be re-invoiced to the
+                customers.
+              </p>
+            </field>
+        </record>
+        
+        <menuitem id="menu_expense_approved" parent="account.menu_finance_payables" action="action_approved_expense" sequence="101"/> 
+        <menuitem id="menu_hr_product" name="Expense Categories" parent="hr.menu_hr_configuration" action="hr_expense_product" groups="base.group_hr_manager"/>
         <menuitem id="next_id_49" name="Expenses" sequence="15" parent="hr.menu_hr_root"/>
         <menuitem action="expense_all" id="menu_expense_all" name="Expenses" parent="next_id_49"/>
-
+        <menuitem action="action_request_approve_expense" id="menu_expense_to_approve" name="Expenses to Approve" parent="next_id_49" groups="base.group_hr_user"/>
+        
     </data>
 </openerp>
index 8084894..a8e2656 100644 (file)
 #
 ##############################################################################
 
+
+import calendar
 import datetime
+from datetime import date
 import math
 import time
 from operator import attrgetter
@@ -111,13 +114,14 @@ class hr_holidays(osv.osv):
     _inherit = ['mail.thread', 'ir.needaction_mixin']
     _track = {
         'state': {
+            'hr_holidays.mt_holidays_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
+            'hr_holidays.mt_holidays_first_validated': lambda self, cr, uid, obj, ctx=None: obj.state == 'validate1',
             'hr_holidays.mt_holidays_approved': lambda self, cr, uid, obj, ctx=None: obj.state == 'validate',
             'hr_holidays.mt_holidays_refused': lambda self, cr, uid, obj, ctx=None: obj.state == 'refuse',
-            'hr_holidays.mt_holidays_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
         },
     }
 
-    def _employee_get(self, cr, uid, context=None):        
+    def _employee_get(self, cr, uid, context=None):
         emp_id = context.get('default_employee_id', False)
         if emp_id:
             return emp_id
@@ -172,6 +176,9 @@ class hr_holidays(osv.osv):
             \nThe status is \'To Approve\', when holiday request is confirmed by user.\
             \nThe status is \'Refused\', when holiday request is refused by manager.\
             \nThe status is \'Approved\', when holiday request is approved by manager.'),
+        'payslip_status': fields.boolean(string='Payslip Status',
+            help='Check this field when the leave has been taken into account in the payslip.'),
+        'report_note': fields.text('Comment by Manager'),
         'user_id':fields.related('employee_id', 'user_id', type='many2one', relation='res.users', string='User', store=True),
         'date_from': fields.datetime('Start Date', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, select=True, copy=False),
         'date_to': fields.datetime('End Date', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, copy=False),
@@ -201,7 +208,8 @@ class hr_holidays(osv.osv):
         'state': 'confirm',
         'type': 'remove',
         'user_id': lambda obj, cr, uid, context: uid,
-        'holiday_type': 'employee'
+        'holiday_type': 'employee',
+        'payslip_status': False,
     }
     _constraints = [
         (_check_date, 'You can not have 2 leaves that overlaps on same day!', ['date_from','date_to']),
@@ -304,7 +312,6 @@ class hr_holidays(osv.osv):
         """
         Update the number_of_days.
         """
-
         # date_to has to be greater than date_from
         if (date_from and date_to) and (date_from > date_to):
             raise osv.except_osv(_('Warning!'),_('The start date must be anterior to the end date.'))
@@ -317,7 +324,6 @@ class hr_holidays(osv.osv):
             result['value']['number_of_days_temp'] = round(math.floor(diff_day))+1
         else:
             result['value']['number_of_days_temp'] = 0
-
         return result
 
     def create(self, cr, uid, values, context=None):
@@ -347,14 +353,13 @@ class hr_holidays(osv.osv):
         obj_emp = self.pool.get('hr.employee')
         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
         manager = ids2 and ids2[0] or False
-        self.holidays_first_validate_notificate(cr, uid, ids, context=context)
-        return self.write(cr, uid, ids, {'state':'validate1', 'manager_id': manager})
+        return self.write(cr, uid, ids, {'state': 'validate1', 'manager_id': manager}, context=context)
 
     def holidays_validate(self, cr, uid, ids, context=None):
         obj_emp = self.pool.get('hr.employee')
         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
         manager = ids2 and ids2[0] or False
-        self.write(cr, uid, ids, {'state':'validate'})
+        self.write(cr, uid, ids, {'state': 'validate'}, context=context)
         data_holiday = self.browse(cr, uid, ids)
         for record in data_holiday:
             if record.double_validation:
@@ -447,23 +452,12 @@ class hr_holidays(osv.osv):
                                 'Please verify also the leaves waiting for validation.'))
         return True
 
-    # -----------------------------
-    # OpenChatter and notifications
-    # -----------------------------
+    def set_payslip_status(self, cr, uid, ids, context=None):
+        return self.write(cr, uid, ids, {'payslip_status': True}, context=context)
 
-    def _needaction_domain_get(self, cr, uid, context=None):
-        emp_obj = self.pool.get('hr.employee')
-        empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
-        dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
-        # if this user is a hr.manager, he should do second validations
-        if self.pool.get('res.users').has_group(cr, uid, 'base.group_hr_manager'):
-            dom = ['|'] + dom + [('state', '=', 'validate1')]
-        return dom
+    def unset_payslip_status(self, cr, uid, ids, context=None):
+        return self.write(cr, uid, ids, {'payslip_status': False}, context=context)
 
-    def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
-        for obj in self.browse(cr, uid, ids, context=context):
-            self.message_post(cr, uid, [obj.id],
-                _("Request approved, waiting second validation."), context=context)
 
 class resource_calendar_leaves(osv.osv):
     _inherit = "resource.calendar.leaves"
@@ -473,9 +467,8 @@ class resource_calendar_leaves(osv.osv):
     }
 
 
-
-class hr_employee(osv.osv):
-    _inherit="hr.employee"
+class hr_employee(osv.Model):
+    _inherit = "hr.employee"
 
     def create(self, cr, uid, vals, context=None):
         # don't pass the value of remaining leave if it's 0 at the creation time, otherwise it will trigger the inverse
@@ -550,23 +543,25 @@ class hr_employee(osv.osv):
         return result
 
     def _leaves_count(self, cr, uid, ids, field_name, arg, context=None):
+        res = {}
         Holidays = self.pool['hr.holidays']
-        return {
-            employee_id: Holidays.search_count(cr,uid, [('employee_id', '=', employee_id)], context=context) 
-            for employee_id in ids
-        }
+        date_begin = date.today().replace(day=1)
+        date_end = date_begin.replace(day=calendar.monthrange(date_begin.year, date_begin.month)[1])
+        for employee_id in ids:
+            leaves = Holidays.search_count(cr, uid, [('employee_id', '=', employee_id), ('type', '=', 'remove')], context=context)
+            approved_leaves = Holidays.search_count(cr, uid, [('employee_id', '=', employee_id), ('type', '=', 'remove'), ('date_from', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)), ('date_from', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)), ('state', '=', 'validate'), ('payslip_status', '=', False)], context=context)
+            res[employee_id] = {'leaves_count': leaves, 'approved_leaves_count': approved_leaves}
+        return res
 
     _columns = {
         'remaining_leaves': fields.function(_get_remaining_days, string='Remaining Legal Leaves', fnct_inv=_set_remaining_days, type="float", help='Total number of legal leaves allocated to this employee, change this value to create allocation/leave request. Total based on all the leave types without overriding limit.'),
-        'current_leave_state': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Status", type="selection",
+        'current_leave_state': fields.function(
+            _get_leave_status, multi="leave_status", string="Current Leave Status", type="selection",
             selection=[('draft', 'New'), ('confirm', 'Waiting Approval'), ('refuse', 'Refused'),
-            ('validate1', 'Waiting Second Approval'), ('validate', 'Approved'), ('cancel', 'Cancelled')]),
-        'current_leave_id': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Type",type='many2one', relation='hr.holidays.status'),
+                       ('validate1', 'Waiting Second Approval'), ('validate', 'Approved'), ('cancel', 'Cancelled')]),
+        'current_leave_id': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Type", type='many2one', relation='hr.holidays.status'),
         'leave_date_from': fields.function(_get_leave_status, multi='leave_status', type='date', string='From Date'),
         'leave_date_to': fields.function(_get_leave_status, multi='leave_status', type='date', string='To Date'),
-        'leaves_count': fields.function(_leaves_count, type='integer', string='Leaves'),
-
+        'leaves_count': fields.function(_leaves_count, multi='_leaves_count', type='integer', string='Number of Leaves (current month)'),
+        'approved_leaves_count': fields.function(_leaves_count, multi='_leaves_count', type='integer', string='Approved Leaves not in Payslip', help="These leaves are approved but not taken into account for payslip"),
     }
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 703fa74..1bdcb13 100644 (file)
@@ -1,6 +1,7 @@
 <?xml version="1.0"?>
 <openerp>
     <data noupdate="1">
+
         <!-- After installation of the module, open the related menu -->
         <record id="base.open_menu" model="ir.actions.todo">
             <field name="action_id" ref="hr.action_client_hr_menu"/>
 
         <!-- Holidays-related subtypes for messaging / Chatter -->
         <record id="mt_holidays_confirmed" model="mail.message.subtype">
-            <field name="name">To Approve</field>
+            <field name="name">Confirmed</field>
+            <field name="res_model">hr.holidays</field>
+            <field name="description">Request created and waiting confirmation</field>
+        </record>
+        <record id="mt_holidays_first_validated" model="mail.message.subtype">
+            <field name="name">Waiting Second Validation</field>
             <field name="res_model">hr.holidays</field>
-            <field name="description">Request confirmed and waiting approval</field>
+            <field name="default" eval="False"/>
+            <field name="description">Request validated, waiting second validation</field>
         </record>
         <record id="mt_holidays_approved" model="mail.message.subtype">
             <field name="name">Approved</field>
             <field name="description">Request refused</field>
         </record>
 
+        <!-- Department related subtypes for messaging / Chatter -->
+        <record id="mt_department_holidays_confirmed" model="mail.message.subtype">
+            <field name="name">Leaves/Allocations: first approval</field>
+            <field name="res_model">hr.department</field>
+            <field name="default" eval="False"/>
+            <field name="parent_id" eval="ref('mt_holidays_confirmed')"/>
+            <field name="relation_field">department_id</field>
+            <field name="sequence" eval="6"/>
+        </record>
+        <record id="mt_department_holidays_first_validated" model="mail.message.subtype">
+            <field name="name">Leaves/Allocations: second approval</field>
+            <field name="res_model">hr.department</field>
+            <field name="default" eval="False"/>
+            <field name="parent_id" eval="ref('mt_holidays_first_validated')"/>
+            <field name="relation_field">department_id</field>
+            <field name="sequence" eval="7"/>
+        </record>
+        <record id="mt_department_holidays_approved" model="mail.message.subtype">
+            <field name="name">Leaves/Allocation Approved</field>
+            <field name="res_model">hr.department</field>
+            <field name="default" eval="False"/>
+            <field name="parent_id" eval="ref('mt_holidays_approved')"/>
+            <field name="relation_field">department_id</field>
+            <field name="sequence" eval="8"/>
+        </record>
+        <record id="mt_department_holidays_refused" model="mail.message.subtype">
+            <field name="name">Leaves/Allocation Refused</field>
+            <field name="res_model">hr.department</field>
+            <field name="default" eval="False"/>
+            <field name="parent_id" eval="ref('mt_holidays_refused')"/>
+            <field name="relation_field">department_id</field>
+            <field name="sequence" eval="9"/>
+        </record>
+
     </data>
 </openerp>
index 5ee1ec8..cc9b931 100644 (file)
@@ -7,10 +7,15 @@
             <field name="arch" type="xml">
                 <search string="Search Leave">
                     <field name="name"/>
+                    <filter domain="[('state','=','draft')]" string="To Confirm"/>
+                    <filter domain="[('state','in',('confirm','validate1'))]" string="To Approve" name="approve"/>
+                    <filter domain="[('state','=','validate')]" string="Validated" name="validated"/>
                     <separator/>
-                    <filter icon="terp-check" domain="[('state','=','draft')]" string="To Confirm"/>
-                    <filter icon="terp-camera_test" domain="[('state','in',('confirm','validate1'))]" string="To Approve" name="approve"/>
-                    <filter icon="terp-camera_test" domain="[('state','=','validate')]" string="Validated" name="validated"/>
+                    <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
+                    <separator/>
+                    <filter string="Approved Leaves" name="validated" domain="[('state','=','validate')]"/>
+                    <separator/>
+                    <filter string="To Report in Payslip" name="gray" domain="[('payslip_status', '=', False)]" groups="base.group_hr_manager"/>
                     <separator/>
                     <filter icon="terp-go-year" name="year" string="Year" domain="[('holiday_status_id.active','=',True)]" help="Filters only on allocations and requests that belong to an holiday type that is 'active' (active field is True)"/>
                     <separator/>
                             <field name="employee_id" attrs="{'required':[('holiday_type','=','employee')],'invisible':[('holiday_type','=','category')]}" on_change="onchange_employee(employee_id)" groups="base.group_hr_user"/>
                             <field name="category_id" attrs="{'required':[('holiday_type','=','category')], 'readonly': [('type', '=', 'remove'),('state','!=','draft'), ('state','!=','confirm')], 'invisible':[('holiday_type','=','employee')]}"/>
                             <field name="department_id" attrs="{'readonly':['|', ('type','=','add'),('holiday_type','=','category')],'invisible':[('holiday_type','=','category')]}" groups="base.group_hr_user"/>
+                            <field name="payslip_status" groups="base.group_hr_manager" attrs="{'invisible':[('type','=','add')]}"/>
                         </group>
+                        <field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..." attrs="{'invisible': [('type', '=', 'remove')]}"/>
+                        <div groups="base.group_hr_manager" attrs="{'invisible':[('type','=','add')]}">
+                            <separator string="Comment by Manager"/>
+                            <field name="report_note" placeholder="e.g. Report to the next month..."/>
+                        </div>
                     </group>
-                    <field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..." attrs="{'invisible': [('type', '=', 'remove')]}"/>
                 </sheet>
                 <div class="oe_chatter">
                     <field name="message_follower_ids" widget="mail_followers"/>
             <field name="name">hr.holidays.allocation.tree</field>
             <field name="model">hr.holidays</field>
             <field name="arch" type="xml">
-                <tree colors="red:state == 'refuse';blue:state == 'draft';black:state in ('confirm','validate','validate1')" string="Allocation Requests">
+                <tree colors="red:state == 'refuse';blue:state == 'draft';black:state in ('confirm','validate','validate1')" string="Allocation Requests"
+                        fonts="bold: message_unread == True">
                     <field name="employee_id"/>
                     <field name="holiday_type"/>
                     <field name="category_id"/>
                     <field name="manager_id" invisible="1"/>
                     <field name="user_id" invisible="1"/>
                     <field name="date_from" invisible="1"/>
-                    <!--field name="type"/-->
+                    <field name="message_unread" invisible="1"/>
                     <field name="state"/>
                 </tree>
             </field>
         </record>
-
-        <!-- Holidays: Leaves Management -->
-        <record model="ir.ui.view" id="allocation_company_new">
-            <field name="name">Leaves Management</field>
+        <record model="ir.ui.view" id="view_holiday_allocation_tree_customize">
+            <field name="name">hr.holidays.allocation.tree.customize</field>
             <field name="model">hr.holidays</field>
             <field name="arch" type="xml">
-                <form string="Leaves Management">
-                    <header>
-                        <button string="Submit to Manager" name="confirm" states="draft" type="workflow" icon="gtk-yes"/>
-                        <button string="Approve" name="validate" states="confirm" type="workflow" icon="gtk-apply"/>
-                        <button string="Refuse" name="refuse" states="confirm,validate,draft" type="workflow" icon="gtk-no"/>
-                        <button string="Reset to Draft" name="reset" states="confirm" type="workflow" groups="base.group_hr_manager"/>
-                        <field name="state"/>
-                    </header>
-                    <group col="4">
-                        <field name="holiday_status_id"/>
-                        <field name="type"/>
-                        <field name="date_from" on_change="onchange_date_from(date_to, date_from)" attrs="{'readonly':[('type','=','add')], 'required':[('type','=','remove')]}"/>
-                        <field name="date_to" on_change="onchange_date_from(date_to, date_from)" attrs="{'readonly':[('type','=','add')], 'required':[('type','=','remove')]}"/>
-                        <field name="number_of_days_temp"/>
-                        <field name="manager_id"/>
-                    </group>
-                    <field name="name" placeholder="Add a reason..."/>
-                </form>
+                <tree string="Allocation Requests" editable="top">
+                    <field name="employee_id"/>
+                    <field name="holiday_type"/>
+                    <field name="holiday_status_id"/>
+                    <field name="name" readonly="1"/>
+                    <field name="date_from" required="1" on_change="onchange_date_from(date_to, date_from)"/>
+                    <field name="date_to" required="1" on_change="onchange_date_to(date_to, date_from)"/>
+                    <field name="number_of_days_temp" string="Allocated Days" sum="Remaining Days"/>
+                    <field name="state"/>
+                    <field name="report_note" groups="base.group_hr_manager"/>
+                    <field name="payslip_status" invisible="1"/>
+                    <button string="To Report in Payslip" name="set_payslip_status"
+                        type="object" class="oe_link oe_right" icon="gtk-normal"
+                        attrs="{'invisible': [('payslip_status', '=', True)]}" groups="base.group_hr_manager"/>
+                    <button string="Reported in last payslips" name="unset_payslip_status"
+                        type="object" class="oe_link oe_right" icon="gtk-yes"
+                        attrs="{'invisible': [('payslip_status', '=', False)]}" groups="base.group_hr_manager"/>
+                </tree>
             </field>
         </record>
 
             <field name="model">hr.holidays</field>
             <field name="priority">20</field>
             <field name="arch" type="xml">
-                <tree colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leaves Summary">
+                <tree colors="red:state == 'refuse';blue:state == 'draft';black:state in ('confirm','validate','validate1')" string="Leaves Summary">
                     <field name="employee_id"/>
                     <field name="category_id" invisible="1"/>
                     <field name="department_id" invisible="1"/>
             </field>
         </record>
 
+        <record model="ir.ui.view" id="view_holiday_employee">
+            <field name="name">hr.holidays.report_employee_tree</field>
+            <field name="model">hr.holidays</field>
+            <field name="priority">21</field>
+            <field name="arch" type="xml">
+                <tree colors="red:state == 'refuse';blue:state == 'draft';black:state in ('confirm','validate','validate1')" string="Employee's Leave">
+                    <field name="employee_id"/>
+                    <field name="type"/>
+                    <field name="name"/>
+                    <field name="number_of_days" string="Number of Days" sum="Remaining Days"/>
+                    <field name="date_from"/>
+                    <field name="date_to"/>
+                    <field name="holiday_status_id"/>
+                    <field name="state"/>
+                    <field name="report_note"/>
+                    <field name="payslip_status" invisible="1"/>
+                    <button string="To Report in Payslip" name="set_payslip_status"
+                        type="object" class="oe_link oe_right"
+                        attrs="{'invisible': [('payslip_status', '=', True)]}" groups="base.group_hr_manager"/>
+                    <button string="Reported in last payslips" name="unset_payslip_status"
+                        type="object" class="oe_link oe_right"
+                        attrs="{'invisible': [('payslip_status', '=', False)]}" groups="base.group_hr_manager"/>
+                </tree>
+            </field>
+        </record>
+
         <record model="ir.ui.view" id="view_holiday">
             <field name="name">hr.holidays.tree</field>
             <field name="model">hr.holidays</field>
             <field name="arch" type="xml">
-                <tree colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leave Requests">
+                <tree colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leave Requests"
+                        fonts="bold: message_unread == True">
                     <field name="employee_id"/>
                     <field name="holiday_type" string="Mode" groups="base.group_no_one"/>
                     <field name="holiday_status_id"/>
                     <field name="date_to"/>
                     <field name="number_of_days" string="Number of Days" sum="Remaining Days"/>
                     <field name="state"/>
+                    <field name="payslip_status" invisible="1"/>
+                    <button string="To Report in Payslip" name="set_payslip_status"
+                        type="object" class="oe_link oe_right"
+                        attrs="{'invisible': [('payslip_status', '=', True)]}" groups="base.group_hr_manager"/>
+                    <button string="Reported in last payslips" name="unset_payslip_status"
+                        type="object" class="oe_link oe_right"
+                        attrs="{'invisible': [('payslip_status', '=', False)]}" groups="base.group_hr_manager"/>
                     <field name="category_id" invisible="1"/>
                     <field name="department_id" invisible="not context.get('set_visible',False)"/>
                     <field name="manager_id" invisible="1"/>
                     <field name="user_id" invisible="1"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
             <field name="res_model">hr.holidays</field>
             <field name="view_type">form</field>
             <field name="view_id" ref="edit_holiday_new"/>
-            <field name="context">{'default_type': 'remove', 'search_default_my_leaves':1}</field>
-            <field name="domain">[('type','=','remove')]</field>
+            <field name="context">{
+                'default_type': 'remove', 
+                'search_default_my_leaves':1,
+                'needaction_menu_ref': 
+                [
+                    'hr_holidays.menu_open_company_allocation', 
+                ]
+            }</field>
+            <field name="domain">[('type','=','remove'), ('employee_id.user_id','=', uid)]</field>
             <field name="search_view_id" ref="view_hr_holidays_filter"/>
             <field name="help" type="html">
               <p class="oe_view_nocontent_create">
             <field name="name">Requests to Approve</field>
             <field name="res_model">hr.holidays</field>
             <field name="view_type">form</field>
-            <field name="context">{'default_type': 'remove', 'search_default_approve':1}</field>
+            <field name="context">{
+                'default_type': 'remove', 
+                'search_default_approve':1, 
+                'needaction_menu_ref': 
+                [
+                    'hr_holidays.menu_open_ask_holidays_new', 
+                    'hr_holidays.menu_open_company_allocation', 
+                    'hr_holidays.menu_open_employee_leave',
+                ]
+            }</field>
             <field name="domain">[('type','=','remove')]</field>
             <field name="view_id" ref="edit_holiday_new"/>
             <field name="search_view_id" ref="view_hr_holidays_filter"/>
             <field name="name">Allocation Requests</field>
             <field name="res_model">hr.holidays</field>
             <field name="view_type">form</field>
-            <field name="context">{'default_type':'add', 'search_default_my_leaves':1}</field>
-            <field name="domain">[('type','=','add')]</field>
+            <field name="context">{
+                'default_type':'add', 
+                'search_default_my_leaves':1,
+                'needaction_menu_ref': 
+                [
+                    'hr_holidays.menu_open_company_allocation', 
+                ]
+            }</field>
+            <field name="domain">[('type','=','add'), ('employee_id.user_id','=', uid)]</field>
             <field name="view_id" ref="edit_holiday_new"/>
             <field name="search_view_id" ref="view_hr_holidays_filter"/>
         </record>
             <field name="name">Allocation Requests to Approve</field>
             <field name="res_model">hr.holidays</field>
             <field name="view_type">form</field>
-            <field name="context">{'default_type': 'add', 'search_default_approve':1}</field>
+            <field name="context">{
+                'default_type': 'add', 
+                'search_default_approve':1, 
+                'needaction_menu_ref': 
+                [
+                    'hr_holidays.menu_open_allocation_holidays', 
+                    'hr_holidays.menu_open_company_allocation'
+                ]
+            }</field>
             <field name="domain">[('type','=','add')]</field>
             <field name="view_id" ref="edit_holiday_new"/>
             <field name="search_view_id" ref="view_hr_holidays_filter"/>
             <field name="view_type">form</field>
             <field name="view_mode">tree,form</field>
             <field name="view_id" eval="view_holiday_simple"/>
-            <field name="context">{'search_default_group_type': 1}</field>
-            <field name="domain">[('holiday_type','=','employee'), ('state', '!=', 'refuse')]</field>
+            <field name="context">{
+                'search_default_group_type': 1, 
+                'search_default_year': 1 ,
+                'needaction_menu_ref': 
+                [
+                    'hr_holidays.menu_open_ask_holidays_new',
+                    'hr_holidays.menu_request_approve_holidays',
+                    'hr_holidays.menu_open_allocation_holidays',
+                    'hr_holidays.menu_request_approve_allocation',
+                    'hr_holidays.menu_open_employee_leave',
+                ]
+            }</field>
+            <field name="domain">[('holiday_type','=','employee')]</field>
             <field name="search_view_id" ref="view_hr_holidays_filter"/>
         </record>
 
         <menuitem name="Leaves Summary" parent="menu_open_ask_holidays" id="menu_open_company_allocation" action="open_company_allocation" sequence="40"/>
+        
+        <record model="ir.actions.act_window" id="open_employee_leaves">
+            <field name="name">Employee's Leaves</field>
+            <field name="res_model">hr.holidays</field>
+            <field name="view_type">form</field>
+            <field name="view_mode">tree,form</field>
+            <field name="view_id" eval="view_holiday_employee"/>
+            <field name="context">{'default_type': 'remove', 'search_default_gray': 1, 'search_default_year': 1, 'search_default_group_employee': 1}</field>
+            <field name="domain">[('type','=','remove')]</field>
+            <field name="search_view_id" ref="view_hr_holidays_filter"/>
+        </record>
+
+        <menuitem name="Employee's Leaves" parent="menu_open_ask_holidays" id="menu_open_employee_leave" groups="base.group_hr_manager,base.group_hr_user" action="open_employee_leaves" sequence="41"/>
 
         <!-- Holidays status -->
         <record id="view_holidays_status_filter" model="ir.ui.view">
             <field name="search_view_id" ref="view_hr_holidays_status_search"/>
         </record>
 
-        <menuitem sequence="3" id="hr.menu_open_view_attendance_reason_config" parent="hr.menu_hr_configuration" name="Leaves"/>
-        <menuitem name="Leaves Types" action="open_view_holiday_status" id="menu_open_view_holiday_status" parent="hr.menu_hr_configuration" sequence="10"/>
+        <menuitem sequence="3" id="hr.menu_open_view_attendance_reason_config" parent="hr.menu_hr_configuration" name="Leaves" groups="base.group_hr_manager"/>
+        <menuitem name="Leaves Types" action="open_view_holiday_status" id="menu_open_view_holiday_status" parent="hr.menu_hr_configuration" sequence="10" groups="base.group_hr_manager"/>
 
         <!-- Holiday on resource leave -->
         <record id="resource_calendar_leave_form_inherit" model="ir.ui.view">
             <field name="domain">[('type','=','remove')]</field>
             <field name="view_id" eval="view_holiday"/>
         </record>
+        
+        <record id="act_hr_employee_holiday_request_approved" model="ir.actions.act_window">
+            <field name="name">Leaves to be reported in Payslip</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">hr.holidays</field>
+            <field name="src_model">hr.employee</field>
+            <field name="view_type">form</field>
+            <field name="view_mode">tree,form</field>
+            <field name="context">{'search_default_employee_id': [active_id], 'search_default_validated': True, 'search_default_gray': True}</field>
+            <field name="domain">[('date_from','&gt;=', context_today().strftime("%Y-%m-1")), ('date_from','&lt;', ((context_today() + relativedelta(months=1)).strftime('%Y-%m-1')) )]</field>
+            <field name="view_id" eval="view_holiday_allocation_tree_customize"/>
+        </record>
 
        <!-- Assing leave -->
         <record id="hr_holidays_leaves_assign_tree_view" model="ir.ui.view">
                     </group>
                 </xpath>
                 <xpath expr="//div[@name='button_box']" position="inside">
+                    <button name="%(act_hr_employee_holiday_request_approved)d"
+                            icon="fa-calendar"
+                            class="oe_stat_button"
+                            type="action"
+                            groups="base.group_hr_user">
+                        <field name="approved_leaves_count" widget="statinfo"/>
+                    </button>
                     <button name="%(act_hr_employee_holiday_request)d" 
-                        type="action"
-                        class="oe_stat_button"
-                        icon="fa-calendar" 
-                        groups="base.group_hr_user">
-                        <field name="leaves_count" widget="statinfo" string="Leaves"/>
+                            type="action"
+                            class="oe_stat_button"
+                            icon="fa-calendar" 
+                            groups="base.group_hr_user">
+                        <field name="leaves_count" widget="statinfo"/>
                     </button>
                 </xpath>
             </field>
index 4908532..caa4430 100644 (file)
@@ -83,6 +83,9 @@ class hr_applicant(osv.Model):
     _inherit = ['mail.thread', 'ir.needaction_mixin']
 
     _track = {
+        'emp_id': {
+            'hr_recruitment.mt_applicant_hired': lambda self, cr, uid, obj, ctx=None: obj.emp_id,
+        },
         'stage_id': {
             # this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
             'hr_recruitment.mt_applicant_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence <= 1,
@@ -232,7 +235,7 @@ class hr_applicant(osv.Model):
         'day_close': fields.function(_compute_day, string='Days to Close', \
                                 multi='day_close', type="float", store=True),
         'color': fields.integer('Color Index'),
-        'emp_id': fields.many2one('hr.employee', string='Employee', help='Employee linked to the applicant.'),
+        'emp_id': fields.many2one('hr.employee', string='Employee', track_visibility='onchange', help='Employee linked to the applicant.'),
         'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True),
         'attachment_number': fields.function(_get_attachment_number, string='Number of Attachments', type="integer"),
     }
@@ -571,6 +574,7 @@ class hr_job(osv.osv):
             'hr.applicant', self._columns['alias_id'], 'name', alias_prefix='job+', alias_defaults={'job_id': 'id'}, context=context)
 
     def create(self, cr, uid, vals, context=None):
+        # TDE note: shouldn't it be in mail_create_nolog ?
         alias_context = dict(context, alias_model_name='hr.applicant', alias_parent_model_name=self._name)
         job_id = super(hr_job, self).create(cr, uid, vals, context=alias_context)
         job = self.browse(cr, uid, job_id, context=context)
@@ -610,5 +614,3 @@ class applicant_category(osv.osv):
     _columns = {
         'name': fields.char('Name', required=True, translate=True),
     }
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index aa08217..3093dea 100644 (file)
         <field name="name">New Applicant</field>
         <field name="res_model">hr.applicant</field>
         <field name="default" eval="False"/>
+        <field name="hidden" eval="True"/>
         <field name="description">Applicant created</field>
     </record>
     <record id="mt_applicant_stage_changed" model="mail.message.subtype">
     <record id="mt_applicant_hired" model="mail.message.subtype">
         <field name="name">Applicant Hired</field>
         <field name="res_model">hr.applicant</field>
-        <field name="default" eval="False"/>
+        <field name="default" eval="True"/>
         <field name="description">Applicant hired</field>
     </record>
+
     <!-- Job-related subtypes for messaging / Chatter -->
     <record id="mt_job_applicant_new" model="mail.message.subtype">
         <field name="name">Applicant Created</field>
     <record id="mt_job_applicant_stage_changed" model="mail.message.subtype">
         <field name="name">Applicant Stage Changed</field>
         <field name="res_model">hr.job</field>
-        <field name="default" eval="True"/>
+        <field name="default" eval="False"/>
         <field name="parent_id" eval="ref('mt_applicant_stage_changed')"/>
         <field name="relation_field">job_id</field>
     </record>
index 21c4af2..82c649e 100644 (file)
@@ -5,6 +5,7 @@
     <menuitem name="Recruitment"
         id="menu_hr_recruitment_recruitment"
         parent="hr.menu_hr_configuration"
+        groups="base.group_hr_manager"
         sequence="40"/>
 
     <act_window
index b6b5254..3a18573 100644 (file)
@@ -92,6 +92,7 @@
   I check that Invoice is created for this timesheet.
 -
   !python {model: account.analytic.line}: |
+    from openerp.tools import float_compare
     aline = self.browse(cr, uid, ref('account_analytic_line_developyamlforhrmodule0'))
     analytic_account_obj = self.pool.get('account.analytic.account')
     data = self.pool.get('hr.timesheet.invoice.create').read(cr, uid, [ref("hr_timesheet_invoice_create_0")], [], context)[0]
     assert product == product_exp
     assert aline.invoice_id, "Invoice created, but analytic line wasn't updated."
     assert aline.invoice_id == invoice_id, "Invoice doesn't match the one at analytic line"
-    assert invoice_id.amount_untaxed == 187.5, "Invoice amount mismatch: %s" % invoice_id.amount_untaxed
-    assert invoice_id.amount_tax == 50, "Invoice tax mismatch: %s" %  invoice_id.amount_tax
+    assert float_compare(invoice_id.amount_untaxed, 187.5, precision_digits=2) == 0, "Invoice amount mismatch: %s" % invoice_id.amount_untaxed
+    assert float_compare(invoice_id.amount_tax, 50, precision_digits=2) == 0, "Invoice tax mismatch: %s" %  invoice_id.amount_tax
index c5b7c9b..dac550a 100644 (file)
@@ -90,6 +90,7 @@
   I check that Invoice is created for this timesheet.
 -
   !python {model: account.analytic.line}: |
+    from openerp.tools import float_compare
     aline = self.browse(cr, uid, ref('account_analytic_line_developyamlforhrmodule1'))
     analytic_account_obj = self.pool.get('account.analytic.account')
     data = self.pool.get('hr.timesheet.invoice.create').read(cr, uid, [ref("hr_timesheet_invoice_create_0")], [], context)[0]
     assert product == product_exp
     assert aline.invoice_id, "Invoice created, but analytic line wasn't updated."
     assert aline.invoice_id == invoice_id, "Invoice doesn't match the one at analytic line"
-    assert invoice_id.amount_untaxed == 187.5, "Invoice amount mismatch: %s" % invoice_id.amount_untaxed
-    assert invoice_id.amount_tax == 40, "Invoice tax mismatch: %s" %  invoice_id.amount_tax
+    assert float_compare(invoice_id.amount_untaxed, 187.5, precision_digits=2) == 0, "Invoice amount mismatch: %s" % invoice_id.amount_untaxed
+    assert float_compare(invoice_id.amount_tax, 40, precision_digits=2) == 0, "Invoice tax mismatch: %s" %  invoice_id.amount_tax
index 96be836..824fb4c 100644 (file)
@@ -31,10 +31,17 @@ from openerp.tools.translate import _
 
 class hr_timesheet_sheet(osv.osv):
     _name = "hr_timesheet_sheet.sheet"
-    _inherit = "mail.thread"
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
     _table = 'hr_timesheet_sheet_sheet'
     _order = "id desc"
-    _description="Timesheet"
+    _description = "Timesheet"
+
+    _track = {
+        'state': {
+            'hr_timesheet_sheet.mt_timesheet_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
+            'hr_timesheet_sheet.mt_timesheet_approved': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
+        },
+    }
 
     def _total(self, cr, uid, ids, name, args, context=None):
         """ Compute the attendances, analytic lines timesheets and differences between them
@@ -165,6 +172,7 @@ class hr_timesheet_sheet(osv.osv):
             ('draft','Open'),
             ('confirm','Waiting Approval'),
             ('done','Approved')], 'Status', select=True, required=True, readonly=True,
+            track_visibility='onchange',
             help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed timesheet. \
                 \n* The \'Confirmed\' status is used for to confirm the timesheet by user. \
                 \n* The \'Done\' status is used when users timesheet is accepted by his/her senior.'),
index 095a92c..4bb9a31 100644 (file)
 
         <menuitem name="My Current Timesheet" id="menu_act_hr_timesheet_sheet_form_my_current" parent="hr_attendance.menu_hr_time_tracking" action="ir_actions_server_timsheet_sheet" sequence="1"/>
 
+        <!-- Timesheet sheet related subtypes for messaging / Chatter -->
+        <record id="mt_timesheet_confirmed" model="mail.message.subtype">
+            <field name="name">Waiting Approval</field>
+            <field name="res_model">hr_timesheet_sheet.sheet</field>
+            <field name="default" eval="True"/>
+            <field name="description">waiting approval</field>
+        </record>
+        <record id="mt_timesheet_approved" model="mail.message.subtype">
+            <field name="name">Approved</field>
+            <field name="res_model">hr_timesheet_sheet.sheet</field>
+            <field name="default" eval="True"/>
+            <field name="description">Timesheet approved</field>
+        </record>
+        <!-- Department (Parent) related subtypes for messaging / Chatter -->
+        <record id="mt_department_timesheet_confirmed" model="mail.message.subtype">
+            <field name="name">Timesheets to Approve</field>
+            <field name="res_model">hr.department</field>
+            <field name="default" eval="False"/>
+            <field name="parent_id" eval="ref('mt_timesheet_confirmed')"/>
+            <field name="relation_field">department_id</field>
+            <field name="sequence" eval="5"/>
+        </record>
+         <record id="mt_department_timesheet_approved" model="mail.message.subtype">
+            <field name="name">Timesheets Approved</field>
+            <field name="res_model">hr.department</field>
+            <field name="default" eval="False"/>
+            <field name="parent_id" eval="ref('mt_timesheet_approved')"/>
+            <field name="relation_field">department_id</field>
+            <field name="sequence" eval="5"/>
+        </record>
+
     </data>
 </openerp>
index afe10cb..7eb5cb3 100644 (file)
                     </notebook>
                 </sheet>
                 <div class="oe_chatter">
-                    <field name="message_ids" widget="mail_thread"/>
                     <field name="message_follower_ids" widget="mail_followers"/>
+                    <field name="message_ids" widget="mail_thread"/>
                 </div>
                 </form>
             </field>
                     <field name="date_from"/>
                     <filter name="new" string="In Draft" domain="[('state','in',('draft', 'new'))]" help="Unvalidated Timesheets"/>
                     <filter name="to_approve" string="To Approve" domain="[('state','=','confirm')]" help="Confirmed Timesheets"/>
+                    <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <field name="employee_id"/>
                     <field name="department_id"/>
                     <group expand="0" string="Group By">
         </record>
 
         <record id="act_hr_timesheet_sheet_form" model="ir.actions.act_window">
-            <field name="name">Timesheets to Validate</field>
+            <field name="name">Timesheets to Approve</field>
             <field name="type">ir.actions.act_window</field>
             <field name="res_model">hr_timesheet_sheet.sheet</field>
             <field name="view_type">form</field>
         </record>
 
         <menuitem action="act_hr_timesheet_sheet_form" id="menu_act_hr_timesheet_sheet_form" parent="hr_attendance.menu_hr_time_tracking"
-             sequence="2" groups="base.group_hr_user"/>
+             sequence="11" groups="base.group_hr_user"/>
 
         <!--
             Company inheritancy
             <field name="model">hr_timesheet_sheet.sheet</field>
             <field eval="10" name="priority"/>
             <field name="arch" type="xml">
-                <tree colors="blue:state == 'draft';black:state in ('confirm','new');gray:state == 'done'" string="Timesheets">
+                <tree colors="blue:state == 'draft';black:state in ('confirm','new');gray:state == 'done'" fonts="bold: message_unread == True" string="Timesheets">
                     <field name="employee_id"/>
                     <field name="date_from"/>
                     <field name="date_to"/>
                     <field name="total_timesheet" widget="float_time"/>
                     <field name="total_difference" widget="float_time" groups="base.group_hr_attendance"/>
                     <field name="state"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
index 6ecdb04..c45e4e4 100644 (file)
@@ -72,10 +72,9 @@ V2.2 specifications.
 If required, you can manually adjust the descriptions via the CODA configuration menu.
 ''',
     'images': ['images/coda_logs.jpeg', 'images/import_coda_logs.jpeg'],
-    'depends': ['account_voucher', 'base_iban', 'l10n_be_invoice_bba'],
+    'depends': ['account_voucher', 'base_iban', 'l10n_be_invoice_bba', 'account_bank_statement_import'],
     'demo': ['l10n_be_coda_demo.xml'],
     'data': [
-        'l10n_be_coda_wizard.xml',
         'l10n_be_coda_view.xml',
     ],
     'auto_install': False,
index dc147ae..6983bc1 100644 (file)
@@ -85,6 +85,5 @@
                </record>
 
         <menuitem name="Bank Statement Lines" parent="account.menu_finance_bank_and_cash" id="menu_account_bank_statement_line_coda" action="action_account_bank_statement_line_coda" sequence="8" groups="base.group_no_one"/>
-       <menuitem name="Import CODA File" parent="account.menu_finance_bank_and_cash" id="menu_account_coda_import" action="action_account_coda_import" sequence="10"/>
        </data> 
 </openerp>
\ No newline at end of file
diff --git a/addons/l10n_be_coda/l10n_be_coda_wizard.xml b/addons/l10n_be_coda/l10n_be_coda_wizard.xml
deleted file mode 100644 (file)
index 627de37..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" ?>
-<openerp>
-    <data>
-
-    <record id="account_coda_import_view" model="ir.ui.view">
-      <field name="name">Import CODA File</field>
-      <field name="model">account.coda.import</field>
-      <field name="priority">1</field>
-      <field name="arch" type="xml">
-        <form string="Import CODA File">
-            <group col="2">
-                <field name="coda_data" filename="coda_fname"/>
-            </group>
-            <footer>
-                <button name="coda_parsing" string="_Import" type="object" class="oe_highlight"/>
-                or
-                <button string="Cancel" class="oe_link" special="cancel"/>
-            </footer>
-        </form>
-      </field>
-    </record>
-
-    <record id="action_account_coda_import" model="ir.actions.act_window">
-      <field name="name">Import CODA File</field>
-      <field name="type">ir.actions.act_window</field>
-      <field name="res_model">account.coda.import</field>
-      <field name="view_type">form</field>
-      <field name="view_mode">form</field>
-      <field name="target">new</field>
-      <field name="view_id" ref="account_coda_import_view"/>
-    </record>
-    
-  </data> 
-</openerp>
diff --git a/addons/l10n_be_coda/test_coda_file/Ontvangen_CODA.2011-01-11-18.59.15.txt b/addons/l10n_be_coda/test_coda_file/Ontvangen_CODA.2011-01-11-18.59.15.txt
deleted file mode 100644 (file)
index d0add85..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-0000011011172505        00178299  DE MEYER LUC              KREDBEBB   00820512013 00000                                       2\r
-12135BE33737018595246                  EUR0000000011812700270710NOVIAT NV                 KBC-Business Comfortrekening       003\r
-2100010000OL44483FW SCTOFBIONLO1000000000435000110111001010000MEDEDELING                                           11011113501 0\r
-2200010000                                                                                        GKCCBEBB                   1 0\r
-2300010000BE41063012345610                     PARTNER 1                                                                     0 1\r
-3100010001OL44483FW SCTOFBIONLO001010001001PARTNER 1                                                                         0 0\r
-2100020000OL4414AC8BOVSOVSOVERS0000000003044450110111001500001101240283842818                                      11011113501 0\r
-2200020000                                                                                        BBRUBEBB                   1 0\r
-2300020000BE61310126985517                     PARTNER 2                                                                     0 1\r
-3100020001OL4414AC8BOVSOVSOVERS001500001001PARTNER 2                                                                         1 0\r
-3200020001MOLENSTRAAT 60                     9340    LEDE                                                                    0 0\r
-2100030000AFECA0CVA IKLINNINNIG1000000000479040110111313410000              KBC-INVESTERINGSKREDIET 737-6543210-21 11011113510 0\r
-2100030001AFECA0CVA IKLINNINNIG1000000000419920110111813410660                                                     11011113500 0\r
-2100030002AFECA0CVA IKLINNINNIG1000000000059120110111813410020                                                     11011113510 0\r
-2100040000AFECA0CVA IKLINNINNIG1000000000479040110111313410000              KBC-INVESTERINGSKREDIET 737-6543210-21 11011113510 0\r
-2100040001AFECA0CVA IKLINNINNIG1000000000419920110111813410660                                                     11011113500 0\r
-2100040002AFECA0CVA IKLINNINNIG1000000000059120110111813410020                                                     11011113510 0\r
-2100050000AOGM00160BSCTOBOGOVER0000000000063740110111001500000TERUGGAVE 37232481 8400083296 .                      11011113501 0\r
-2200050000                                                     362/363                            KREDBEBB                   1 0\r
-2300050000BE43730004200601                     KBC VERZEKERINGEN NV                                                          0 1\r
-3100050001AOGM00160BSCTOBOGOVER001500001001KBC VERZEKERINGEN NV                                                              1 0\r
-3200050001VAN OVERSTRAETENPLEIN 2            3000    LEUVEN                                                                  0 0\r
-8135BE44734024486445                  EUR0000000013527810110111                                                                0\r
-9               000022000000001393080000000003108190                                                                           2\r
diff --git a/addons/l10n_be_coda/test_coda_file/Ontvangen_CODA.2013-01-11-18.59.15.txt b/addons/l10n_be_coda/test_coda_file/Ontvangen_CODA.2013-01-11-18.59.15.txt
new file mode 100644 (file)
index 0000000..d0add85
--- /dev/null
@@ -0,0 +1,24 @@
+0000011011172505        00178299  DE MEYER LUC              KREDBEBB   00820512013 00000                                       2\r
+12135BE33737018595246                  EUR0000000011812700270710NOVIAT NV                 KBC-Business Comfortrekening       003\r
+2100010000OL44483FW SCTOFBIONLO1000000000435000110111001010000MEDEDELING                                           11011113501 0\r
+2200010000                                                                                        GKCCBEBB                   1 0\r
+2300010000BE41063012345610                     PARTNER 1                                                                     0 1\r
+3100010001OL44483FW SCTOFBIONLO001010001001PARTNER 1                                                                         0 0\r
+2100020000OL4414AC8BOVSOVSOVERS0000000003044450110111001500001101240283842818                                      11011113501 0\r
+2200020000                                                                                        BBRUBEBB                   1 0\r
+2300020000BE61310126985517                     PARTNER 2                                                                     0 1\r
+3100020001OL4414AC8BOVSOVSOVERS001500001001PARTNER 2                                                                         1 0\r
+3200020001MOLENSTRAAT 60                     9340    LEDE                                                                    0 0\r
+2100030000AFECA0CVA IKLINNINNIG1000000000479040110111313410000              KBC-INVESTERINGSKREDIET 737-6543210-21 11011113510 0\r
+2100030001AFECA0CVA IKLINNINNIG1000000000419920110111813410660                                                     11011113500 0\r
+2100030002AFECA0CVA IKLINNINNIG1000000000059120110111813410020                                                     11011113510 0\r
+2100040000AFECA0CVA IKLINNINNIG1000000000479040110111313410000              KBC-INVESTERINGSKREDIET 737-6543210-21 11011113510 0\r
+2100040001AFECA0CVA IKLINNINNIG1000000000419920110111813410660                                                     11011113500 0\r
+2100040002AFECA0CVA IKLINNINNIG1000000000059120110111813410020                                                     11011113510 0\r
+2100050000AOGM00160BSCTOBOGOVER0000000000063740110111001500000TERUGGAVE 37232481 8400083296 .                      11011113501 0\r
+2200050000                                                     362/363                            KREDBEBB                   1 0\r
+2300050000BE43730004200601                     KBC VERZEKERINGEN NV                                                          0 1\r
+3100050001AOGM00160BSCTOBOGOVER001500001001KBC VERZEKERINGEN NV                                                              1 0\r
+3200050001VAN OVERSTRAETENPLEIN 2            3000    LEUVEN                                                                  0 0\r
+8135BE44734024486445                  EUR0000000013527810110111                                                                0\r
+9               000022000000001393080000000003108190                                                                           2\r
diff --git a/addons/l10n_be_coda/tests/__init__.py b/addons/l10n_be_coda/tests/__init__.py
new file mode 100644 (file)
index 0000000..05b23c4
--- /dev/null
@@ -0,0 +1,5 @@
+from . import test_import_bank_statement
+checks = [
+    test_import_bank_statement
+]
+
diff --git a/addons/l10n_be_coda/tests/test_import_bank_statement.py b/addons/l10n_be_coda/tests/test_import_bank_statement.py
new file mode 100644 (file)
index 0000000..759b6d3
--- /dev/null
@@ -0,0 +1,39 @@
+from openerp.tests.common import TransactionCase
+from openerp.modules.module import get_module_resource
+
+class TestCodaFile(TransactionCase):
+    """Tests for import bank statement coda file format (account.bank.statement.import)
+    """
+
+    def setUp(self):
+        super(TestCodaFile, self).setUp()
+        self.statement_import_model = self.registry('account.bank.statement.import')
+        self.bank_statement_model = self.registry('account.bank.statement')
+
+    def test_coda_file_import(self):
+        cr, uid = self.cr, self.uid
+        bank_temp_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'account', 'conf_bnk')
+        partner_id_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'main_partner')
+        company_id_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'main_company')
+        self.bank_temp_id = bank_temp_ref and bank_temp_ref[1] or False
+        self.partner_id = partner_id_ref and partner_id_ref[1] or False
+        self.company_id = company_id_ref and company_id_ref[1] or False
+        coda_file_path = get_module_resource('l10n_be_coda', 'test_coda_file', 'Ontvangen_CODA.2013-01-11-18.59.15.txt')
+        coda_file = open(coda_file_path, 'rb').read().encode('base64')
+        bank_account_id = self.registry('res.partner.bank').create(cr, uid, dict(
+                        state = 'bank',
+                        acc_number = 'BE33737018595246',
+                        bank_name = 'Reserve',
+                        partner_id = self.partner_id,
+                        company_id = self.company_id
+                        ))
+        bank_statement_id = self.statement_import_model.create(cr, uid, dict(
+                        file_type = 'coda',
+                        data_file = coda_file,
+                         ))
+        self.statement_import_model.parse_file(cr, uid, [bank_statement_id])
+        statement_id = self.bank_statement_model.search(cr, uid, [('name', '=', '135')])[0]
+        bank_st_record = self.bank_statement_model.browse(cr, uid, statement_id)
+        self.assertEquals(bank_st_record.balance_start, 11812.70)
+        self.assertEquals(bank_st_record.balance_end_real, 13527.81)
+
index 2670a44..5790e50 100644 (file)
@@ -22,7 +22,7 @@
 import base64
 import time
 
-from openerp.osv import fields, osv
+from openerp.osv import osv
 from openerp.tools.translate import _
 from openerp import tools
 
@@ -30,33 +30,16 @@ import logging
 
 _logger = logging.getLogger(__name__)
 
-class account_coda_import(osv.osv_memory):
-    _name = 'account.coda.import'
-    _description = 'Import CODA File'
-    _columns = {
-        'coda_data': fields.binary('CODA File', required=True),
-        'coda_fname': fields.char('CODA Filename', required=True),
-        'note': fields.text('Log'),
-    }
+from openerp.addons.account_bank_statement_import import account_bank_statement_import as coda_ibs
 
-    _defaults = {
-        'coda_fname': lambda *a: '',
-    }
+coda_ibs.add_file_type(('coda', 'CODA'))
 
-    def coda_parsing(self, cr, uid, ids, context=None, batch=False, codafile=None, codafilename=None):
+class account_bank_statement_import(osv.TransientModel):
+    _inherit = "account.bank.statement.import"
+
+    def process_coda(self, cr, uid, codafile=None, journal_id=False, context=None):
         if context is None:
             context = {}
-        if batch:
-            codafile = str(codafile)
-            codafilename = codafilename
-        else:
-            data = self.browse(cr, uid, ids)[0]
-            try:
-                codafile = data.coda_data
-                codafilename = data.coda_fname
-            except:
-                raise osv.except_osv(_('Error'), _('Wizard in incorrect state. Please hit the Cancel button'))
-                return {}
         recordlist = unicode(base64.decodestring(codafile), 'windows-1252', 'strict').split('\n')
         statements = []
         for line in recordlist:
@@ -119,7 +102,7 @@ class account_coda_import(osv.osv_memory):
                     raise osv.except_osv(_('Error') + ' R1004', _("No matching Bank Account (with Account Journal) found.\n\nPlease set-up a Bank Account with as Account Number '%s' and as Currency '%s' and an Account Journal.") % (statement['acc_number'], statement['currency']))
                 statement['description'] = rmspaces(line[90:125])
                 statement['balance_start'] = float(rmspaces(line[43:58])) / 1000
-                if line[42] == '1':    #1 = Debit, the starting balance is negative
+                if line[42] == '1':  # 1 = Debit, the starting balance is negative
                     statement['balance_start'] = - statement['balance_start']
                 statement['balance_start_date'] = time.strftime(tools.DEFAULT_SERVER_DATE_FORMAT, time.strptime(rmspaces(line[58:64]), '%d%m%y'))
                 statement['accountHolder'] = rmspaces(line[64:90])
@@ -241,6 +224,7 @@ class account_coda_import(osv.osv_memory):
                     statement['balance_end_real'] = statement['balance_start'] + statement['balancePlus'] - statement['balanceMin']
         for i, statement in enumerate(statements):
             statement['coda_note'] = ''
+            statement_line = []
             balance_start_check_date = (len(statement['lines']) > 0 and statement['lines'][0]['entryDate']) or statement['date']
             cr.execute('SELECT balance_end_real \
                 FROM account_bank_statement \
@@ -248,7 +232,7 @@ class account_coda_import(osv.osv_memory):
                 ORDER BY date DESC,id DESC LIMIT 1', (statement['journal_id'].id, balance_start_check_date))
             res = cr.fetchone()
             balance_start_check = res and res[0]
-            if balance_start_check == None:
+            if balance_start_check is None:
                 if statement['journal_id'].default_debit_account_id and (statement['journal_id'].default_credit_account_id == statement['journal_id'].default_debit_account_id):
                     balance_start_check = statement['journal_id'].default_debit_account_id.balance
                 else:
@@ -257,7 +241,7 @@ class account_coda_import(osv.osv_memory):
                 statement['coda_note'] = _("The CODA Statement %s Starting Balance (%.2f) does not correspond with the previous Closing Balance (%.2f) in journal %s!") % (statement['description'] + ' #' + statement['paperSeqNumber'], statement['balance_start'], balance_start_check, statement['journal_id'].name)
             if not(statement.get('period_id')):
                 raise osv.except_osv(_('Error') + ' R3006', _(' No transactions or no period in coda file !'))
-            data = {
+            statement_data = {
                 'name': statement['paperSeqNumber'],
                 'date': statement['date'],
                 'journal_id': statement['journal_id'].id,
@@ -265,7 +249,6 @@ class account_coda_import(osv.osv_memory):
                 'balance_start': statement['balance_start'],
                 'balance_end_real': statement['balance_end_real'],
             }
-            statement['id'] = self.pool.get('account.bank.statement').create(cr, uid, data, context=context)
             for line in statement['lines']:
                 if line['type'] == 'information':
                     statement['coda_note'] = "\n".join([statement['coda_note'], line['type'].title() + ' with Ref. ' + str(line['ref']), 'Date: ' + str(line['entryDate']), 'Communication: ' + line['communication'], ''])
@@ -291,55 +274,30 @@ class account_coda_import(osv.osv_memory):
                     if 'counterpartyAddress' in line and line['counterpartyAddress'] != '':
                         note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress'])
                     line['name'] = "\n".join(filter(None, [line['counterpartyName'], line['communication']]))
-                    partner_id = None
                     structured_com = ""
-                    bank_account_id = False
                     if line['communication_struct'] and 'communication_type' in line and line['communication_type'] == '101':
                         structured_com = line['communication']
+                    bank_account_id = False
+                    partner_id = False
                     if 'counterpartyNumber' in line and line['counterpartyNumber']:
-                        ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', str(line['counterpartyNumber']))])
-                        if ids:
-                            bank_account_id = ids[0]
-                            partner_id = self.pool.get('res.partner.bank').browse(cr, uid, bank_account_id, context=context).partner_id.id
-                        else:
-                            #create the bank account, not linked to any partner. The reconciliation will link the partner manually
-                            #chosen at the bank statement final confirmation time.
-                            try:
-                                type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal')
-                                type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context)
-                                bank_code = type_id.code
-                            except ValueError:
-                                bank_code = 'bank'
-                            bank_account_id = self.pool.get('res.partner.bank').create(cr, uid, {'acc_number': str(line['counterpartyNumber']), 'state': bank_code}, context=context)
+                        bank_account_id, partner_id = self._detect_partner(cr, uid, str(line['counterpartyNumber']), identifying_field='acc_number', context=context)
                     if 'communication' in line and line['communication'] != '':
                         note.append(_('Communication') + ': ' + line['communication'])
-                    data = {
+                    line_data = {
                         'name': line['name'],
                         'note': "\n".join(note),
                         'date': line['entryDate'],
                         'amount': line['amount'],
                         'partner_id': partner_id,
-                        'statement_id': statement['id'],
                         'ref': structured_com,
                         'sequence': line['sequence'],
                         'bank_account_id': bank_account_id,
                     }
-                    self.pool.get('account.bank.statement.line').create(cr, uid, data, context=context)
+                    statement_line.append((0, 0, line_data))
             if statement['coda_note'] != '':
-                self.pool.get('account.bank.statement').write(cr, uid, [statement['id']], {'coda_note': statement['coda_note']}, context=context)
-        model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'action_bank_statement_tree')
-        action = self.pool[model].browse(cr, uid, action_id, context=context)
-        return {
-            'name': action.name,
-            'view_type': action.view_type,
-            'view_mode': action.view_mode,
-            'res_model': action.res_model,
-            'domain': action.domain,
-            'context': action.context,
-            'type': 'ir.actions.act_window',
-            'search_view_id': action.search_view_id.id,
-            'views': [(v.view_id.id, v.view_mode) for v in action.view_ids]
-        }
+                statement_data.update({'coda_note': statement['coda_note']})
+            statement_data.update({'journal_id': journal_id, 'line_ids': statement_line})
+        return [statement_data]
 
 
 def rmspaces(s):
index b1cf490..8e10402 100644 (file)
@@ -50,12 +50,12 @@ Main Features
         'wizard/invite_view.xml',
         'wizard/mail_compose_message_view.xml',
         'mail_message_subtype.xml',
-        'res_config_view.xml',
         'mail_message_view.xml',
         'mail_mail_view.xml',
         'mail_followers_view.xml',
         'mail_thread_view.xml',
         'mail_group_view.xml',
+        'res_config_view.xml',
         'res_partner_view.xml',
         'data/mail_data.xml',
         'data/mail_group_data.xml',
index aeea2c0..3409c98 100644 (file)
@@ -30,6 +30,7 @@ from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
 from openerp.osv import fields, osv
 from openerp.tools.safe_eval import safe_eval as eval
 from openerp.tools.translate import _
+import openerp.tools as tools
 
 _logger = logging.getLogger(__name__)
 
@@ -60,6 +61,7 @@ class mail_mail(osv.Model):
         'email_cc': fields.char('Cc', help='Carbon copy message recipients'),
         'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
         'headers': fields.text('Headers', copy=False),
+        'failure_reason': fields.text('Failure Reason', help="Failure reason. This is usually the exception thrown by the email server, stored to ease the debugging of mailing issues.", readonly=1),
         # Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
         # and during unlink() we will not cascade delete the parent and its attachments
         'notification': fields.boolean('Is Notification',
@@ -296,7 +298,7 @@ class mail_mail(osv.Model):
                     mail.write({'state': 'sent', 'message_id': res})
                     mail_sent = True
                 else:
-                    mail.write({'state': 'exception'})
+                    mail.write({'state': 'exception', 'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.')})
                     mail_sent = False
 
                 # /!\ can't use mail.state here, as mail.refresh() will cause an error
@@ -312,8 +314,9 @@ class mail_mail(osv.Model):
                                   mail.id, mail.message_id)
                 raise
             except Exception as e:
-                _logger.exception('failed sending mail.mail %s', mail.id)
-                mail.write({'state': 'exception'})
+                failure_reason = tools.ustr(e)
+                _logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason)
+                mail.write({'state': 'exception', 'failure_reason': failure_reason})
                 self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=False)
                 if raise_exception:
                     if isinstance(e, AssertionError):
index 233f407..bf51684 100644 (file)
@@ -52,6 +52,9 @@
                             <page string="Attachments">
                                 <field name="attachment_ids"/>
                             </page>
+                            <page string="Failure Reason" attrs="{'invisible': [('state', '!=', 'exception')]}">
+                                <field name="failure_reason"/>
+                            </page>
                         </notebook>
                     </sheet>
                 </form>
             <field name="res_model">mail.mail</field>
             <field name="view_type">form</field>
             <field name="view_mode">tree,form</field>
-            <field name="context">{'search_default_outgoing': 1}</field>
+            <field name="context">{}</field>
             <field name="search_view_id" ref="view_mail_search"/>
         </record>
 
index bac956a..6aed3de 100644 (file)
@@ -20,7 +20,9 @@
 ##############################################################################
 
 import urlparse
+import datetime
 
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
 from openerp.osv import osv, fields
 
 
@@ -28,11 +30,18 @@ class project_configuration(osv.TransientModel):
     _inherit = 'base.config.settings'
 
     _columns = {
+        'fail_counter': fields.integer('Fail Mail', readonly=True),
         'alias_domain': fields.char('Alias Domain',
                                      help="If you have setup a catch-all email domain redirected to "
                                           "the Odoo server, enter the domain name here."),
     }
 
+    def get_default_fail_counter(self, cr, uid, ids, context=None):
+        previous_date = datetime.datetime.now() - datetime.timedelta(days=30)
+        return {
+            'fail_counter': self.pool.get('mail.mail').search(cr, uid, [('date', '>=', previous_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)), ('state', '=', 'exception')], count=True, context=context),
+        }
+
     def get_default_alias_domain(self, cr, uid, ids, context=None):
         alias_domain = self.pool.get("ir.config_parameter").get_param(cr, uid, "mail.catchall.domain", default=None, context=context)
         if alias_domain is None:
index 6f75667..441f42b 100644 (file)
@@ -6,6 +6,15 @@
             <field name="model">base.config.settings</field>
             <field name="inherit_id" ref="base_setup.view_general_configuration"/>
             <field name="arch" type="xml">
+                <xpath expr="//button[@string='Configure outgoing email servers']" position='after'>
+                    <button class="oe_inline oe_link" style="display: inline-block;" name= "%(action_view_mail_mail)d" type="action" 
+                        context="{'search_default_exception': 1, 'search_default_outgoing': 0}"
+                        attrs="{'invisible': [('fail_counter','=',0)]}">
+                        <span> -- </span>
+                        <i class="fa fa-exclamation-triangle"></i> <field class="oe_inline" name="fail_counter"/>
+                        <span>failed emails</span>
+                    </button>
+                </xpath>
                 <xpath expr="//div[@name='email']" position='inside'>
                     <div>
                         <label for="alias_domain" class="oe_inline"/>
index 8d461af..e7da638 100644 (file)
@@ -93,7 +93,6 @@
 .openerp .oe_mail .oe_msg .oe_msg_content .oe_msg_body .oe_mail_cleaned {
     display: none;
 }
-
 /* a) Indented Messages */
 
 .openerp .oe_mail .oe_msg_indented{
index 84e0d42..2a283a9 100644 (file)
@@ -621,7 +621,8 @@ class test_mail(TestMail):
         mail_compose.send_mail(cr, user_raoul.id, [compose_id], {'mail_post_autofollow': True, 'mail_create_nosubscribe': True})
         group_pigs.refresh()
         message = group_pigs.message_ids[0]
-
+        # Test: mail_mail: notifications have been deleted
+        self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', message.id)]),'message_send: mail.mail message should have been auto-deleted!')
         # Test: mail.group: followers (c and d added by auto follow key; raoul not added by nosubscribe key)
         pigs_pids = [p.id for p in group_pigs.message_follower_ids]
         test_pids = [self.partner_admin_id, p_b_id, p_c_id, p_d_id]
@@ -703,6 +704,12 @@ class test_mail(TestMail):
 
         # Test: Pigs and Bird did receive their message
         test_msg_ids = self.mail_message.search(cr, uid, [], limit=2)
+        mail_ids = self.mail_mail.search(cr, uid, [('mail_message_id', '=', message2.id)])
+        mail_record_id = self.mail_mail.browse(cr, uid, mail_ids)[0]
+        self.assertTrue(mail_record_id, "'message_send: mail.mail message should have in processing mail queue'" )
+        #check mass mail state...
+        test_mail_ids = self.mail_mail.search(cr, uid, [('state', '=', 'exception')])
+        self.assertNotIn(mail_ids, test_mail_ids, 'compose wizard: Mail sending Failed!!')
         self.assertIn(message1.id, test_msg_ids, 'compose wizard: Pigs did not receive its mass mailing message')
         self.assertIn(message2.id, test_msg_ids, 'compose wizard: Bird did not receive its mass mailing message')
 
index 81db37c..6d15ac8 100644 (file)
                         or
                         <button string="Cancel" type="object" name="cancel" class="oe_link"/>
                     </header>
-                    <separator string="Mass Mailing"/>
-                    <group>
-                        <label for="id" string="Settings"/>
-                        <div>
-                            <div name="module_mass_mailing">
-                                <field name="module_mass_mailing" class="oe_inline"/>
-                                <label for="module_mass_mailing"/>
+                    <div name="config_setting">        
+                        <separator string="Mass Mailing"/>
+                        <group>
+                            <label for="id" string="Settings"/>
+                            <div>
+                                <div name="module_mass_mailing">
+                                    <field name="module_mass_mailing" class="oe_inline"/>
+                                    <label for="module_mass_mailing"/>
+                                </div>
                             </div>
-                        </div>
-                    </group>
-                    <separator string="Marketing Campaigns"/>
-                    <group>
-                        <label for="id" string="Settings"/>
-                        <div>
-                            <div name="module_marketing_campaign">
-                                <field name="module_marketing_campaign" class="oe_inline"/>
-                                <label for="module_marketing_campaign"/>
+                        </group>
+                        <separator string="Marketing Campaigns"/>
+                        <group>
+                            <label for="id" string="Settings"/>
+                            <div>
+                                <div name="module_marketing_campaign">
+                                    <field name="module_marketing_campaign" class="oe_inline"/>
+                                    <label for="module_marketing_campaign"/>
+                                </div>
                             </div>
-                        </div>
-                    </group>
+                        </group>
+                     </div>
                 </form>
             </field>
         </record>
index bc55f61..b42992d 100644 (file)
@@ -5,8 +5,12 @@
 <template id="head" inherit_id="website.layout" name="Mail customization">
     <xpath expr="//head" position="inside">
         <script type="text/javascript" src="/mass_mailing/static/src/js/website_mass_mailing.editor.js" groups="base.group_website_publisher"></script>
+    </xpath>
+</template>
+
+<template id="assets_frontend" inherit_id="website.assets_frontend" name="Mail Frontend Assets">
+    <xpath expr="." position="inside">
         <script type="text/javascript" src="/mass_mailing/static/src/js/website_mass_mailing.js"></script>
-        <link rel='stylesheet' href='/website_mail/static/src/css/website_mail.css'/>
     </xpath>
 </template>
 
index fb2a971..d694c34 100644 (file)
@@ -1,14 +1,9 @@
 # -*- coding: utf-8 -*-
 import logging
-import simplejson
-import os
-import openerp
-import time
-import random
 
 from openerp import http
 from openerp.http import request
-from openerp.addons.web.controllers.main import module_boot, login_redirect
+from openerp.addons.web.controllers.main import login_redirect
 
 _logger = logging.getLogger(__name__)
 
@@ -21,19 +16,4 @@ class PosController(http.Controller):
         if not request.session.uid:
             return login_redirect()
 
-        modules =  simplejson.dumps(module_boot(request.db))
-        init =  """
-                 var wc = new s.web.WebClient();
-                 wc.show_application = function(){
-                     wc.action_manager.do_action("pos.ui");
-                 };
-                 wc.setElement($(document.body));
-                 wc.start();
-                 """
-
-        html = request.registry.get('ir.ui.view').render(request.cr, request.session.uid,'point_of_sale.index',{
-            'modules': modules,
-            'init': init,
-        })
-
-        return html
+        return request.render('point_of_sale.index')
index efc46df..8e42eca 100644 (file)
@@ -67,7 +67,8 @@ class pos_order_report(osv.osv):
                     l.product_id as product_id
                 from pos_order_line as l
                     left join pos_order s on (s.id=l.order_id)
-                    left join product_template pt on (pt.id=l.product_id)
+                    left join product_product p on (l.product_id=p.id)
+                    left join product_template pt on (p.product_tmpl_id=pt.id)
                     left join product_uom u on (u.id=pt.uom_id)
                 group by
                     s.date_order, s.partner_id,s.state,
index 4bd433a..a5c5279 100644 (file)
@@ -7,14 +7,12 @@
             <field name="model">res.partner</field>
             <field name="inherit_id" ref="base.view_partner_form"/>
             <field name="arch" type="xml">
-                <notebook position="inside">
-                    <page string="Point of Sale"> 
-                        <group>
-                            <field name="ean13" />
-                            <button name="edit_ean" type="object" string="Edit" />
-                        </group>
-                    </page>
-                </notebook>
+                <group name="point_of_sale" position="replace">
+                    <group string="Point of Sale">
+                        <field name="ean13" />
+                        <button name="edit_ean" type="object" string="Edit" />
+                    </group>
+                </group>
             </field>
         </record>
 
index ea63b24..90442a9 100644 (file)
@@ -47,5 +47,7 @@
 -
   I test that the total of the attached invoice is correct
 -
-  !assert {model: pos.order, id: pos_order_pos1, string: Invoice inconsistent with its origin order}:
-     - invoice_id.amount_total == 1795.5
+  !python {model: pos.order}: |
+     from openerp.tools import float_compare
+     amount_total = self.browse(cr, uid, ref('pos_order_pos1')).amount_total
+     assert float_compare(amount_total, 1752.75, precision_digits=2) == 0, "Invoice not correct"
index 8dd27d7..9a7ee7f 100644 (file)
         <t t-call-assets="web.assets_common" t-css="false" />
         <t t-call-assets="web.assets_backend" t-css="false" />
 
-        <script type="text/javascript" id="loading-script">
+        <script type="text/javascript" id="loading-script" t-raw="init">
             $(function() {
-            var s = new openerp.init(<t t-raw='modules' />);
-                <t t-raw='init' />
+                var s = new openerp.init();
+                var wc = new s.web.WebClient();
+
+                wc.show_application = function() {
+                    wc.action_manager.do_action("pos.ui");
+                };
+
+                wc.setElement($(document.body));
+                wc.start();
             });
         </script>
 
index 95332cf..f22359a 100644 (file)
@@ -6,8 +6,8 @@
             <field name="model">res.partner</field>
             <field name="inherit_id" ref="base.view_partner_form"/>
             <field name="arch" type="xml">
-                <page string="Sales &amp; Purchases" position="inside">
-                    <group>
+                <page name="sales_purchases" position="inside">
+                    <group name="property_invoice_type" position="after">
                         <group name="pricelists" attrs="{'invisible': [('is_company','=',False),('parent_id','!=',False)]}">
                             <field name="property_product_pricelist" groups="product.group_sale_pricelist"/>
                         </group>
index 53fa22d..6380e66 100644 (file)
@@ -4,60 +4,64 @@
   I check sale price of Assemble Computer
 -
   !python {model: product.product}: |
+    from openerp.tools import float_compare
     context.update({'pricelist': ref("customer_pricelist"), 'quantity':1})
     product = self.browse(cr, uid, ref("product_product_4"), context=context)
-    assert product.price == (product.lst_price-product.lst_price*(0.10)), "Wrong sale price: Assemble Computer."
+    assert float_compare(product.price, (product.lst_price-product.lst_price*(0.10)), precision_digits=2) == 0, "Wrong sale price: Assemble Computer."
 -
   I check sale price of Laptop.
 -
   !python {model: product.product}: |
+    from openerp.tools import float_compare
     product = self.browse(cr, uid, ref("product_product_25"), context=context)
-    assert product.price == product.lst_price + 1, "Wrong sale price: Laptop."
+    assert float_compare(product.price, product.lst_price + 1, precision_digits=2) == 0, "Wrong sale price: Laptop."
 -
   I check sale price of IT component.
 -
   !python {model: product.product}: |
+    from openerp.tools import float_compare
     product = self.browse(cr, uid, ref("product_product_7"), context=context)
-    assert product.price == product.lst_price, "Wrong sale price: IT component."
-
+    assert float_compare(product.price, product.lst_price, precision_digits=2) == 0, "Wrong sale price: IT component."
 -
   I check sale price of IT component if more than 3 Unit.
 -
   !python {model: product.product}: |
+    from openerp.tools import float_compare
     context.update({'quantity':5})
     product = self.browse(cr, uid, ref("product_product_26"), context=context)
-    assert product.price == product.lst_price-product.lst_price*(0.05), "Wrong sale price: IT component if more than 3 Unit."
+    assert float_compare(product.price, product.lst_price-product.lst_price*(0.05), precision_digits=2) == 0, "Wrong sale price: IT component if more than 3 Unit."
 -
   I check sale price of LCD Monitor.
 -
   !python {model: product.product}: |
+    from openerp.tools import float_compare
     context.update({'quantity':1})
     product = self.browse(cr, uid, ref("product_product_6"), context=context)
-    assert product.price == product.lst_price, "Wrong sale price: LCD Monitor."
-
+    assert float_compare(product.price, product.lst_price, precision_digits=2) == 0, "Wrong sale price: LCD Monitor."
 -
   I check sale price of LCD Monitor on end of year.
 -
   !python {model: product.product}: |
+    from openerp.tools import float_compare
     context.update({'quantity':1, 'date': '2011-12-31'})
     product = self.browse(cr, uid, ref("product_product_6"), context=context)
-    assert product.price == product.lst_price-product.lst_price*(0.30), "Wrong sale price: LCD Monitor on end of year."
-
+    assert float_compare(product.price, product.lst_price-product.lst_price*(0.30), precision_digits=2) == 0, "Wrong sale price: LCD Monitor on end of year."
 -
   I check cost price of LCD Monitor.
 -
   !python {model: product.product}: |
+    from openerp.tools import float_compare
     context.update({'quantity':1, 'date': False, 'partner': ref('base.res_partner_4'), 'pricelist': ref("supplier_pricelist")})
     product = self.browse(cr, uid, ref("product_product_6"), context=context)
-    assert product.price == 792, "wrong cost price: LCD Monitor."
+    assert float_compare(product.price, 792, precision_digits=2) == 0, "Wrong cost price: LCD Monitor."
 -
   I check cost price of LCD Monitor if more than 3 Unit.
 -
   !python {model: product.product}: |
+    from openerp.tools import float_compare
     context.update({'quantity':3})
     product = self.browse(cr, uid, ref("product_product_6"), context=context)
-    assert product.price == 787, "wrong cost price: LCD Monitor if more than 3 Unit."
-
+    assert float_compare(product.price, 787, precision_digits=2) == 0, "Wrong cost price: LCD Monitor if more than 3 Unit."
 -
  I print the sale prices report.
 -
index 5beb80f..e9a92a1 100644 (file)
@@ -531,7 +531,11 @@ def Project():
 
         project_id = super(project, self).create(cr, uid, vals, context=create_context)
         project_rec = self.browse(cr, uid, project_id, context=context)
-        self.pool.get('mail.alias').write(cr, uid, [project_rec.alias_id.id], {'alias_parent_thread_id': project_id, 'alias_defaults': {'project_id': project_id}}, context)
+        ir_values = self.pool.get('ir.values').get_default( cr, uid, 'project.config.settings', 'generate_project_alias' )
+        values = { 'alias_parent_thread_id': project_id, 'alias_defaults': {'project_id': project_id}}
+        if ir_values:
+            values = dict(values, alias_name=vals['name'])
+        self.pool.get('mail.alias').write(cr, uid, [project_rec.alias_id.id], values, context=context)
         return project_id
 
     def write(self, cr, uid, ids, vals, context=None):
index 187ad03..b7bcda2 100644 (file)
@@ -58,6 +58,8 @@ class project_configuration(osv.osv_memory):
         'group_manage_delegation_task': fields.boolean("Allow task delegation",
             implied_group='project.group_delegate_task',
             help="Allows you to delegate tasks to other users."),
+        'generate_project_alias': fields.boolean("Automatically generate an email alias at the project creation",
+            help="Odoo will generate an email alias at the project creation from project name."),
     }
 
     def get_default_time_unit(self, cr, uid, fields, context=None):
@@ -74,4 +76,8 @@ class project_configuration(osv.osv_memory):
             return {'value': {'group_tasks_work_on_tasks': True}}
         return {}
 
+    def set_default_generate_project_alias(self, cr, uid, ids, context=None):
+        config_value = self.browse(cr, uid, ids, context=context).generate_project_alias
+        self.pool.get('ir.values').set_default(cr, uid, 'project.config.settings', 'generate_project_alias', config_value)
+
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 16dce54..7db447c 100644 (file)
                             </div>
                         </div>
                     </group>
+                    <group>
+                        <label for="id" string="Communication"/>
+                        <div>
+                            <div>
+                                <field name="generate_project_alias" class="oe_inline"/>
+                                <label for="generate_project_alias" />
+                            </div>
+                        </div>
+                    </group>
                     <separator string="Helpdesk &amp; Support"/>
                     <group>
                         <label for="id" string="Support"/>
index 6d585d6..f97a20a 100644 (file)
@@ -37,6 +37,7 @@
   Then I import a sample EDI document of a sale order (v7.0)
 -
   !python {model: edi.edi}: |
+    from openerp.tools import float_compare
     purchase_order_pool = self.pool.get('purchase.order')
     edi_document = {
         "__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_test", 
     assert bank_info.acc_number == "Guys bank: 123477777-156113", 'Expected "Guys bank: 123477777-156113", got %s' % bank_info.acc_number
 
     assert order_new.pricelist_id.name == 'Default Purchase Pricelist' , "Default Purchase Pricelist was not automatically assigned"
-    assert order_new.amount_total == 350, "Amount total is not same"
-    assert order_new.amount_untaxed == 350, "untaxed amount is not same"
+    assert float_compare(order_new.amount_total, 350, precision_digits=2) == 0, "Amount total is not same"
+    assert float_compare(order_new.amount_untaxed, 350, precision_digits=2) == 0, "untaxed amount is not same"
     assert len(order_new.order_line) == 2, "Purchase order lines number mismatch"
     for purchase_line in order_new.order_line:
         if purchase_line.name == 'PC Assemble SC234':
             assert purchase_line.product_uom.name == "Unit" , "uom is not same"
-            assert purchase_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(purchase_line.price_unit,)
+            assert float_compare(purchase_line.price_unit, 150 , precision_digits=2) == 0, "unit price is not same, got %s, expected 150"%(purchase_line.price_unit,)
             assert purchase_line.product_qty == 1 , "product qty is not same"
         elif purchase_line.name == 'PC on Demand':
             assert purchase_line.product_uom.name == "Unit" , "uom is not same"
-            assert purchase_line.price_unit == 20 , "unit price is not same, got %s, expected 20"%(purchase_line.price_unit,)
+            assert float_compare(purchase_line.price_unit, 20 , precision_digits=2) == 0, "unit price is not same, got %s, expected 20"%(purchase_line.price_unit,)
             assert purchase_line.product_qty == 10 , "product qty is not same"
         else:
             raise AssertionError('unknown order line: %s' % purchase_line)
   "Then I import a sample EDI document of a sale order (v6.1 - to test backwards compatibility)"
 -
   !python {model: edi.edi}: |
+    from openerp.tools import float_compare
     purchase_order_pool = self.pool.get('purchase.order')
     edi_document = { 
         "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_test",
     assert bank_info.acc_number == "Another bank: 123477700-156113", 'Expected "Another bank: 123477700-156113", got %s' % bank_info.acc_number
 
     assert order_new.pricelist_id.name == 'Default Purchase Pricelist' , "Default Purchase Pricelist was not automatically assigned"
-    assert order_new.amount_total == 350, "Amount total is not same"
-    assert order_new.amount_untaxed == 350, "untaxed amount is not same"
+    assert float_compare(order_new.amount_total, 350, precision_digits=2) == 0, "Amount total is not same"
+    assert float_compare(order_new.amount_untaxed, 350, precision_digits=2) == 0, "untaxed amount is not same"
     assert len(order_new.order_line) == 2, "Purchase order lines number mismatch"
     for purchase_line in order_new.order_line:
         if purchase_line.name == 'Basic PC':
             assert purchase_line.product_uom.name == "PCE" , "uom is not same"
-            assert purchase_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(purchase_line.price_unit,)
+            assert float_compare(purchase_line.price_unit, 150 , precision_digits=2) == 0, "unit price is not same, got %s, expected 150"%(purchase_line.price_unit,)
             assert purchase_line.product_qty == 1 , "product qty is not same"
         elif purchase_line.name == 'Medium PC':
             assert purchase_line.product_uom.name == "PCE" , "uom is not same"
-            assert purchase_line.price_unit == 20 , "unit price is not same, got %s, expected 20"%(purchase_line.price_unit,)
+            assert float_compare(purchase_line.price_unit, 20 , precision_digits=2) == 0, "unit price is not same, got %s, expected 20"%(purchase_line.price_unit,)
             assert purchase_line.product_qty == 10 , "product qty is not same"
         else:
             raise AssertionError('unknown order line: %s' % purchase_line)
index bf48e33..d4c4a9d 100644 (file)
@@ -3,8 +3,10 @@
 -
   I check the total untaxed amount of the RFQ is correctly computed
 -
-  !assert {model: purchase.order, id: purchase_order_1, string: The amount of RFQ is not correctly computed}:
-    - round(sum([l.price_subtotal for l in order_line]), 2) == round(amount_untaxed, 2)
+  !python {model: purchase.order}: |
+    from openerp.tools import float_compare
+    po = self.browse(cr, uid, ref('purchase_order_1'))
+    assert float_compare(sum([l.price_subtotal for l in po.order_line]), po.amount_untaxed, precision_digits=2) == 0, "The amount of RFQ is not correctly computed"
 -
   I confirm the RFQ.
 -
@@ -18,6 +20,7 @@
   I check that the invoice details which is generated after confirmed RFQ.
 -
   !python {model: purchase.order}: |
+    from openerp.tools import float_compare
     purchase_order = self.browse(cr, uid, ref("purchase_order_1"))
     assert len(purchase_order.invoice_ids) >= 1, "Invoice is not generated more or less than one"
     for invoice in purchase_order.invoice_ids:
@@ -28,9 +31,9 @@
       assert invoice.currency_id.id == purchase_order.pricelist_id.currency_id.id ,"Invoice currency is not correspond with purchase order"
       assert invoice.origin == purchase_order.name,"Invoice origin is not correspond with purchase order"
       assert invoice.company_id.id == purchase_order.company_id.id ,"Invoice company is not correspond with purchase order"
-      assert invoice.amount_untaxed == purchase_order.amount_untaxed, "Invoice untaxed amount is not correspond with purchase order"
-      assert invoice.amount_tax == purchase_order.amount_tax, "Invoice tax amount is not correspond with purchase order"
-      assert invoice.amount_total == purchase_order.amount_total, "Invoice total amount is not correspond with purchase order"
+      assert float_compare(invoice.amount_untaxed, purchase_order.amount_untaxed, precision_digits=2) == 0, "Invoice untaxed amount is not correspond with purchase order"
+      assert float_compare(invoice.amount_tax, purchase_order.amount_tax, precision_digits=2) == 0, "Invoice tax amount is not correspond with purchase order"
+      assert float_compare(invoice.amount_total, purchase_order.amount_total, precision_digits=2) == 0, "Invoice total amount is not correspond with purchase order"
       assert len(invoice.invoice_line) == len(purchase_order.order_line), "Lines of Invoice and Purchase Order are not correspond"
 -
   I check that Reception details after confirmed RFQ.
index af5aade..97b0875 100644 (file)
@@ -21,6 +21,7 @@
   I check requisition details which created after run procurement.
 -
   !python {model: procurement.order}: |
+    from openerp.tools import float_compare
     procurement_ids = self.search(cr, uid, [('requisition_id','!=', False)])
     for procurement in self.browse(cr, uid, procurement_ids, context=context):
         requisition = procurement.requisition_id
@@ -29,7 +30,7 @@
         line = requisition.line_ids[0]
         assert line.product_id.id == procurement.product_id.id, "Product is not correspond."
         assert line.product_uom_id.id == procurement.product_uom.id, "UOM is not correspond."
-        assert line.product_qty == procurement.product_qty, "Quantity is not correspond."
+        assert float_compare(line.product_qty, procurement.product_qty, precision_digits=2) == 0, "Quantity is not correspond."
 -
   I send the purchase order associated to the requisition.
 -
index b085c07..618f322 100644 (file)
   First, I  generate the resource detail and check the resource Efficiency assigned.
 -
   !python {model: resource.resource}: |
+    from openerp.tools import float_compare
     resources = self.generate_resources(cr, uid, [ref('base.user_root'),ref('base.user_demo')], ref('timesheet_group1'), context)
     for resource in resources.values():
-        assert resource.get('efficiency', 0.0) == 1.0, 'Invalid resource generated.'
+        assert float_compare(resource.get('efficiency', 0.0), 1.0, precision_digits=2) == 0, 'Invalid resource generated.'
 -
   I check per day work hour availability of the Resource based on Working Calendar Assigned to each resource, for first day of the week.
 -
   !python {model: resource.resource}: |
+    from openerp.tools import float_compare
     calendar_pool = self.pool.get('resource.calendar')
     resources = self.browse(cr, uid,  [ref('resource_analyst'), ref('resource_designer'), ref('resource_developer')], context)
     from datetime import datetime, timedelta
@@ -26,7 +28,7 @@
     dt = now - timedelta(days=now.weekday()) 
     for resource in resources:
         result = calendar_pool.working_hours_on_day(cr, uid, resource.calendar_id, dt, context)
-        assert result == 9.0, 'Wrong calculation of day work hour availability of the Resource (found %d).' % result
+        assert float_compare(result, 9.0, precision_digits=2) == 0, 'Wrong calculation of day work hour availability of the Resource (found %d).' % result
 -
   Now, resource "Developer" drafted leave on Thursday in this week.
 -
index 0db6b9c..03a2b57 100644 (file)
@@ -174,7 +174,7 @@ class sale_order(osv.osv):
         'name': fields.char('Order Reference', required=True, copy=False,
             readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, select=True),
         'origin': fields.char('Source Document', help="Reference of the document that generated this sales order request."),
-        'client_order_ref': fields.char('Reference/Description', copy=False),
+        'client_order_ref': fields.char('Customer Reference', copy=False),
         'state': fields.selection([
             ('draft', 'Draft Quotation'),
             ('sent', 'Quotation Sent'),
index bcb69f9..1176d07 100644 (file)
@@ -365,7 +365,7 @@ Thanks!</field>
             <field name="journal_id" ref="account.sales_journal"/>
             <field name="section_id" ref="sales_team.section_sales_department"/>
             <field name="state">draft</field>
-            <field name="type">in_invoice</field>
+            <field name="type">out_invoice</field>
             <field name="account_id" ref="account.a_recv"/>
             <field name="name">Test invoice 1</field>
         </record>
index b2c89ef..f974ec5 100644 (file)
                     <header>
                         <button name="invoice_recreate" states="invoice_except" string="Recreate Invoice" groups="base.group_user"/>
                         <button name="invoice_corrected" states="invoice_except" string="Ignore Exception" groups="base.group_user"/>
+                        <button name="action_button_confirm" states="sent" string="Confirm Sale" class="oe_highlight" type="object" groups="base.group_user"/>
                         <button name="action_quotation_send" string="Send by Email" type="object" states="draft" class="oe_highlight" groups="base.group_user"/>
                         <button name="action_quotation_send" string="Send by Email" type="object" states="sent,progress,manual" groups="base.group_user"/>
                         <button name="print_quotation" string="Print" type="object" states="draft" class="oe_highlight" groups="base.group_user"/>
                         <button name="print_quotation" string="Print" type="object" states="sent,progress,manual" groups="base.group_user"/>
                         <button name="action_button_confirm" states="draft" string="Confirm Sale" type="object" groups="base.group_user"/>
-                        <button name="action_button_confirm" states="sent" string="Confirm Sale" class="oe_highlight" type="object" groups="base.group_user"/>
                         <button name="action_view_invoice" string="View Invoice" type="object" class="oe_highlight"
                             attrs="{'invisible': [('invoice_exists', '=', False)]}" groups="base.group_user"/>
                         <button name="%(action_view_sale_advance_payment_inv)d" string="Create Invoice"
                             type="action" states="manual" class="oe_highlight" groups="base.group_user"/>
                         <button name="copy_quotation" states="cancel" string="New Copy of Quotation" type="object"/>
-                        <button name="cancel" states="draft,sent" string="Cancel Quotation" groups="base.group_user"/>
+                        <button name="cancel" states="draft,sent" string="Cancel" groups="base.group_user"/>
                         <button name="action_cancel" states="manual,progress" string="Cancel Order" type="object" groups="base.group_user"/>
                         <button name="invoice_cancel" states="invoice_except" string="Cancel Order" groups="base.group_user"/>
                         <field name="state" widget="statusbar" statusbar_visible="draft,sent,progress,done" statusbar_colors='{"invoice_except":"red","waiting_date":"blue"}'/>
                         </group>
                         <group>
                             <field name="date_order"/>
-                            <field name="client_order_ref"/>
                             <field domain="[('type','=','sale')]" name="pricelist_id" groups="product.group_sale_pricelist" on_change="onchange_pricelist_id(pricelist_id,order_line)"/>
                             <field name="currency_id" invisible="1"/>
                         </group>
                                     <field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice', 'base.group_sale_salesman_all_leads']}"/>
                                     <field name="section_id" options="{'no_create': True}" groups="base.group_multi_salesteams"/>
                                     <field groups="base.group_no_one" name="origin"/>
+                                    <field name="client_order_ref"/>
                                 </group>
                                 <group name="sale_pay">
                                     <field name="payment_term" options="{'no_create': True}"/>
index 65b6eb7..4eb7ebb 100644 (file)
@@ -35,6 +35,7 @@
   "Then I import a sample EDI document of a purchase order (v7.0)"
 -
   !python {model: edi.edi}: |
+    from openerp.tools import float_compare
     sale_order_pool = self.pool.get('sale.order')
     edi_document = {
         "__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_test",
     assert bank_info.acc_number == "Another bank: 032465700-156700", 'Expected "Another bank: 032465700-156700", got %s' % bank_info.acc_number
 
     assert order_new.pricelist_id.name == 'Public Pricelist' , "Public Price list was not automatically assigned"
-    assert order_new.amount_total == 350, "Amount total is wrong"
-    assert order_new.amount_untaxed == 350, "Untaxed amount is wrong"
+    assert float_compare(order_new.amount_total, 350, precision_digits=2) == 0, "Amount total is wrong"
+    assert float_compare(order_new.amount_untaxed, 350, precision_digits=2) == 0, "Untaxed amount is wrong"
     assert len(order_new.order_line) == 2, "Sale order lines mismatch"
     for sale_line in order_new.order_line:
         if sale_line.name == 'PC Assemble SC234':
             assert sale_line.product_uom.name == "Unit" , "uom is not same"
-            assert sale_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(sale_line.price_unit,)
+            assert float_compare(sale_line.price_unit, 150 , precision_digits=2) == 0, "unit price is not same, got %s, expected 150"%(sale_line.price_unit,)
             assert sale_line.product_uom_qty == 1 , "product qty is not same"
         elif sale_line.name == 'PC on Demand':
             assert sale_line.product_uom.name == "Unit" , "uom is not same"
-            assert sale_line.price_unit == 100 , "unit price is not same, got %s, expected 100"%(sale_line.price_unit,)
+            assert float_compare(sale_line.price_unit, 100, precision_digits=2) == 0, "unit price is not same, got %s, expected 100"%(sale_line.price_unit,)
             assert sale_line.product_uom_qty == 2 , "product qty is not same"
         else:
             raise AssertionError('unknown order line: %s' % sale_line)
   "Then I import a sample EDI document of a purchase order (v6.1 - to test backwards compatibility)"
 -
   !python {model: edi.edi}: |
+    from openerp.tools import float_compare
     sale_order_pool = self.pool.get('sale.order')
     edi_document = {
         "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_test",
     assert bank_info.acc_number == "Ladies bank: 032465789-156113", 'Expected "Ladies bank: 032465789-156113", got %s' % bank_info.acc_number
 
     assert order_new.pricelist_id.name == 'Public Pricelist' , "Public Price list was not automatically assigned"
-    assert order_new.amount_total == 350, "Amount total is wrong"
-    assert order_new.amount_untaxed == 350, "Untaxed amount is wrong"
+    assert float_compare(order_new.amount_total, 350, precision_digits=2) == 0, "Amount total is wrong"
+    assert float_compare(order_new.amount_untaxed, 350, precision_digits=2) == 0, "Untaxed amount is wrong"
     assert len(order_new.order_line) == 2, "Sale order lines mismatch"
     for sale_line in order_new.order_line:
         if sale_line.name == 'Basic PC':
             assert sale_line.product_uom.name == "PCE" , "uom is not same"
-            assert sale_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(sale_line.price_unit,)
+            assert float_compare(sale_line.price_unit, 150, precision_digits=2) == 0, "unit price is not same, got %s, expected 150"%(sale_line.price_unit,)
             assert sale_line.product_uom_qty == 1 , "product qty is not same"
         elif sale_line.name == 'Medium PC':
             assert sale_line.product_uom.name == "PCE" , "uom is not same"
-            assert sale_line.price_unit == 100 , "unit price is not same, got %s, expected 100"%(sale_line.price_unit,)
+            assert float_compare(sale_line.price_unit, 100 , precision_digits=2) == 0, "unit price is not same, got %s, expected 100"%(sale_line.price_unit,)
             assert sale_line.product_uom_qty == 2 , "product qty is not same"
         else:
             raise AssertionError('unknown order line: %s' % sale_line)
index c911815..1488b2f 100644 (file)
 -
   I verify that the onchange was correctly triggered
 -
-  !assert {model: sale.order, id: sale.sale_order_test1, string: The onchange function of product was not correctly triggered}:
-    - order_line[0].name == u'[A8767] Apple In-Ear Headphones'
-    - order_line[0].price_unit == 79.0
-    - order_line[0].product_uom_qty == 8
-    - order_line[0].product_uom.id == ref('product.product_uom_unit')
+  !python {model: sale.order}: |
+    from openerp.tools import float_compare
+    order_line = self.browse(cr, uid, ref('sale.sale_order_test1')).order_line
+    assert order_line[0].name == u'[A8767] Apple In-Ear Headphones', "The onchange function of product was not correctly triggered"
+    assert float_compare(order_line[0].price_unit, 79.0, precision_digits=2) == 0, "The onchange function of product was not correctly triggered"
+    assert order_line[0].product_uom_qty == 8, "The onchange function of product was not correctly triggered"
+    assert order_line[0].product_uom.id == ref('product.product_uom_unit'), "The onchange function of product was not correctly triggered"
 
 -
   I create another sale order
 -
   I verify that the onchange was correctly triggered
 -
-  !assert {model: sale.order, id: sale.sale_order_test2, string: The onchange function of product was not correctly triggered}:
-    - order_line[0].name == u'[A8767] Apple In-Ear Headphones'
-    - order_line[0].price_unit == 79.0 * 12
-    - order_line[0].product_uom.id == ref('product.product_uom_dozen')
-    - order_line[0].product_uom_qty == 16
\ No newline at end of file
+  !python {model: sale.order}: |
+    from openerp.tools import float_compare
+    order_line = self.browse(cr, uid, ref('sale.sale_order_test2')).order_line
+    assert order_line[0].name == u'[A8767] Apple In-Ear Headphones', "The onchange function of product was not correctly triggered"
+    assert float_compare(order_line[0].price_unit, 79.0 * 12, precision_digits=2) == 0, "The onchange function of product was not correctly triggered"
+    assert order_line[0].product_uom.id == ref('product.product_uom_dozen'), "The onchange function of product was not correctly triggered"
+    assert order_line[0].product_uom_qty == 16, "The onchange function of product was not correctly triggered"
+
index d49f090..2408641 100644 (file)
@@ -29,7 +29,7 @@
                 <field name="user_id" position="after">
                     <field name="categ_ids" widget="many2many_tags"/>
                 </field>
-                <field name="origin" position="after">
+                <field name="client_order_ref" position="after">
                     <field name="campaign_id" />
                     <field name="medium_id" />
                     <field name="source_id" />
index 9dfad95..8457874 100644 (file)
             <field name="model">res.partner</field>
             <field name="inherit_id" ref="base.view_partner_form"/>
             <field name="arch" type="xml">
-                <page string="Sales &amp; Purchases" position="inside">
-                    <group colspan="2" col="2" attrs="{'invisible': [('is_company','=',False),('parent_id','!=',False)]}">
-                        <separator string="Invoicing" colspan="2"/>
-                        <field name="property_invoice_type"/>
+                <group name="invoicing" position="replace">
+                    <group string="Invoicing">
+                        <group cols="2" colspan="2" attrs="{'invisible': [('is_company','=',False),('parent_id','!=',False)]}">
+                            <field name="property_invoice_type"/>
+                        </group>
                     </group>
-                </page>
+                </group>
             </field>
         </record>
 
index 8de8ea4..06a0f4d 100644 (file)
 -
   First I check the total amount of the Quotation before Approved.
 -
-  !assert {model: sale.order, id: sale_order_service, string: The amount of the Quotation is not correctly computed}:
-    - sum([l.price_subtotal for l in order_line]) == amount_untaxed
+  !python {model: sale.order}: |
+    from openerp.tools import float_compare
+    so = self.browse(cr, uid, ref('sale_order_service'))
+    float_compare(sum([l.price_subtotal for l in so.order_line]), so.amount_untaxed, precision_digits=2) == 0, "The amount of the Quotation is not correctly computed"
 - 
   I set an explicit invoicing partner that is different from the main SO Customer
 -
   I check the invoice details after dispatched delivery.
 -
   !python {model: sale.order}: |
+    from openerp.tools import float_compare
     order = self.browse(cr, uid, ref("sale_order_service"))
     assert order.invoice_ids, "Invoice is not created."
     ac = order.partner_invoice_id.property_account_receivable.id
         assert inv_line.product_id.id == so_line.product_id.id or False,"Product is not correspond"
         assert inv_line.account_id.id == ac,"Account of Invoice line is not corresponding."
         assert inv_line.uos_id.id == (so_line.product_uos and so_line.product_uos.id or so_line.product_uom.id), "Product UOS is not correspond."
-        assert inv_line.price_unit == so_line.price_unit , "Price Unit is not correspond."
+        assert float_compare(inv_line.price_unit, so_line.price_unit , precision_digits=2) == 0, "Price Unit is not correspond."
         assert inv_line.quantity == (so_line.product_uos and so_line.product_uos_qty or so_line.product_uom_qty), "Product qty is not correspond."
         assert inv_line.price_subtotal == so_line.price_subtotal, "Price sub total is not correspond."
 -
index 7cb4da5..f01b8ba 100644 (file)
@@ -25,6 +25,8 @@
 -
   I verify that the onchange of product on sale order line was correctly triggered
 -
-  !assert {model: sale.order, id: sale_order_onchange1, string: The onchange function of product was not correctly triggered}:
-    - order_line[0].name == u'Devil Worship Book'
-    - order_line[0].price_unit == 66.6
+  !python {model: sale.order}: |
+    from openerp.tools import float_compare
+    order_line = self.browse(cr, uid, ref('sale_order_onchange1')).order_line
+    assert order_line[0].name == u'Devil Worship Book', "The onchange function of product was not correctly triggered"
+    assert float_compare(order_line[0].price_unit, 66.6, precision_digits=2) == 0, "The onchange function of product was not correctly triggered"
index ad8aa88..4453116 100644 (file)
@@ -1,74 +1,16 @@
 # -*- coding: utf-8 -*-
 import logging
-import simplejson
 
 from openerp import http
 from openerp.http import request
-from openerp.addons.web.controllers.main import module_boot
 
 _logger = logging.getLogger(__name__)
 
-html_template = """<!DOCTYPE html>
-<html>
-    <head>
-        <title>Barcode Scanner</title>
-
-        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
-        <meta http-equiv="content-type" content="text/html, charset=utf-8" />
-
-        <meta name="viewport" content=" width=1024, user-scalable=no">
-        <meta name="apple-mobile-web-app-capable" content="yes">
-        <meta name="mobile-web-app-capable" content="yes">
-
-        <link rel="shortcut icon"    sizes="80x51" href="/stock/static/src/img/scan.png">
-        <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
-        <link rel="stylesheet" href="/stock/static/src/css/barcode.css" />
-        <link rel="stylesheet" href="/web/static/lib/bootstrap/css/bootstrap.css" />
-        <link rel="stylesheet" href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css" />
-        <link rel="stylesheet" href="/web/static/lib/fontawesome/css/font-awesome.css" />
-        <script type="text/javascript" src="/web/js/web.assets_common"></script>
-        <script type="text/javascript" src="/web/js/web.assets_backend"></script>
-        <script type="text/javascript">
-            $(function() {
-                var s = new openerp.init(%(modules)s);
-                %(init)s
-            });
-        </script>
-    </head>
-    <body>
-        <div class="openerp openerp_webclient_container">
-            <table class="oe_webclient">
-                <tr>
-                    <td class="oe_application"/>
-                </tr>
-            </table>
-        </div>
-
-        <!--[if lte IE 8]>
-        <script src="//ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
-        <script>CFInstall.check({mode: "overlay"});</script>
-        <![endif]-->
-    </body>
-</html>
-"""
-
 class BarcodeController(http.Controller):
 
-    @http.route(['/barcode/web/'], type='http', auth='user')
+    @http.route(['/stock/barcode/'], type='http', auth='user')
     def a(self, debug=False, **k):
         if not request.session.uid:
-            return http.local_redirect('/web/login?redirect=/barcode/web')
+            return http.local_redirect('/web/login?redirect=/stock/barcode/')
 
-        r = html_template % {
-            'modules': simplejson.dumps(module_boot(request.db)),
-            'init': """
-                    var wc = new s.web.WebClient();
-                    wc.show_application = function(){
-                        wc.action_manager.do_action("stock.ui", {});
-                    };
-                    wc.do_push_state = function(state){};
-                    wc.setElement($(document.body));
-                    wc.start();
-                     """
-        }
-        return r
+        return request.render('stock.barcode_index')
index c437298..a924266 100644 (file)
@@ -238,7 +238,7 @@ To see a calculation of the lead times, take the example of the cabinet above. S
 Bill of Materials for 1 SHE100 Unit
 
 
-+-------------+----------+------------
++-------------+----------+-----------+
 | Product Code| Quantity | UoM       |
 +====================================+
 |SIDEPAN      |        2 | PCE       |
@@ -253,11 +253,11 @@ Bill of Materials for 1 SHE100 Unit
 Bill of Materials for 2 SIDEPAN Units
 
 
-+-------------+----------+------------
++-------------+----------+-----------+
 | Product Code| Quantity | UoM       |
 +====================================+
 | WOOD002     |      0.17| M         |
-+-------------+----------+------------
++-------------+----------+-----------+
 
 The SIDEPAN is made from an order using the workflow shown. The WOOD002 is purchased on order and the other products are all found in stock. An order for the product SHE100 will then generate two production orders (SHE100 and SIDEPAN) then produce two purchase orders for the product WOOD002. Product WOOD002 is used in the production of both SHE100 and SIDEPAN. Set the lead times on the product forms to the following:
 
index 69d833d..19fa5e3 100644 (file)
         <field name="model">res.partner</field>
         <field name="inherit_id" ref="product.view_partner_property_form"/>
         <field name="arch" type="xml">
-            <group name="pricelists" position="after">
-                <group groups="stock.group_locations">
+            <xpath expr="//group[@name='mailing']" position="after">
+                <group groups="stock.group_locations" string="Warehouse">
                     <field name="property_stock_customer" domain="[('usage','=','customer')]"/>
                     <field name="property_stock_supplier" domain="[('usage','=','supplier')]"/>
                 </group>
-            </group>
+            </xpath>
         </field>
     </record>
 
index 80d3ba6..c54505e 100644 (file)
@@ -28,11 +28,6 @@ function openerp_picking_widgets(instance){
             var self = this;
             this.rows = [];
             this.search_filter = "";
-            jQuery.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) {
-                return function( elem ) {
-                    return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
-                };
-            });
         },
         get_header: function(){
             return this.getParent().get_header();
index 42dad7d..8f81bb9 100644 (file)
@@ -1069,7 +1069,7 @@ class stock_picking(osv.osv):
 
     @api.cr_uid_ids_context
     def open_barcode_interface(self, cr, uid, picking_ids, context=None):
-        final_url="/barcode/web/#action=stock.ui&picking_id="+str(picking_ids[0])
+        final_url="/stock/barcode/#action=stock.ui&picking_id="+str(picking_ids[0])
         return {'type': 'ir.actions.act_url', 'url':final_url, 'target': 'self',}
 
     @api.cr_uid_ids_context
@@ -4040,7 +4040,7 @@ class stock_picking_type(osv.osv):
     _order = 'sequence'
 
     def open_barcode_interface(self, cr, uid, ids, context=None):
-        final_url = "/barcode/web/#action=stock.ui&picking_type_id=" + str(ids[0]) if len(ids) else '0'
+        final_url = "/stock/barcode/#action=stock.ui&picking_type_id=" + str(ids[0]) if len(ids) else '0'
         return {'type': 'ir.actions.act_url', 'url': final_url, 'target': 'self'}
 
     def _get_tristate_values(self, cr, uid, ids, field_name, arg, context=None):
index a38fe9b..f5bca7a 100644 (file)
@@ -3,11 +3,64 @@
 -->
 <openerp>
     <data>
-        <template id="assets_backend" name="stock assets" inherit_id="web.assets_backend">
-            <xpath expr="." position="inside">
-                <link rel="stylesheet" href="/stock/static/src/css/stock.css"/>
-                <script type="text/javascript" src="/stock/static/src/js/widgets.js"></script>
-            </xpath>
-        </template>
+
+<template id="assets_backend" name="stock assets" inherit_id="web.assets_backend">
+    <xpath expr="." position="inside">
+        <link rel="stylesheet" href="/stock/static/src/css/stock.css"/>
+        <script type="text/javascript" src="/stock/static/src/js/widgets.js"></script>
+    </xpath>
+</template>
+
+<template id="barcode_index" name="Barcode Scanner">&lt;!DOCTYPE html&gt;
+<html>
+    <head>
+        <title>Barcode Scanner</title>
+
+        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
+        <meta http-equiv="content-type" content="text/html, charset=utf-8" />
+
+        <meta name="viewport" content=" width=1024, user-scalable=no"/>
+        <meta name="apple-mobile-web-app-capable" content="yes"/>
+        <meta name="mobile-web-app-capable" content="yes"/>
+
+        <link rel="shortcut icon"    sizes="80x51" href="/stock/static/src/img/scan.png"/>
+        <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
+
+        <link rel="stylesheet" href="/stock/static/src/css/barcode.css" />
+        <link rel="stylesheet" href="/web/static/lib/bootstrap/css/bootstrap.css" />
+        <link rel="stylesheet" href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css" />
+        <link rel="stylesheet" href="/web/static/lib/fontawesome/css/font-awesome.css" />
+
+        <t t-call-assets="web.assets_common" t-css="false" />
+        <t t-call-assets="web.assets_backend" t-css="false" />
+
+        <script type="text/javascript" id="loading-script" t-raw="init">
+            $(function() {
+                var s = new openerp.init();
+                var wc = new s.web.WebClient();
+
+                wc.show_application = function() {
+                    wc.action_manager.do_action("stock.ui", {});
+                };
+
+                wc.do_push_state = function(state){};
+                wc.setElement($(document.body));
+                wc.start();
+            });
+        </script>
+
+    </head>
+    <body>
+        <div class='openerp openerp_webclient_container'>
+            <table class='oe_webclient'>
+                <tr>
+                    <td class='oe_application' />
+                </tr>
+            </table>
+        </div>
+    </body>
+</html>
+</template>
+
     </data>
 </openerp>
index f190fef..77be9fd 100644 (file)
@@ -1517,6 +1517,13 @@ openerp.time_to_str = function(obj) {
          + lpad(obj.getSeconds(),2);
 };
 
+// jQuery custom plugins
+jQuery.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) {
+    return function( elem ) {
+        return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
+    };
+});
+
 openerp.declare = declare;
 
 return openerp;
index bebb59d..fec189e 100644 (file)
@@ -430,7 +430,7 @@ var Tour = {
                 return Tour.error(next, "Can't reach the next step");
             }
         }
-        checkNext();
+        setTimeout(checkNext, 0);
     },
     nextStep: function (step) {
         var state = Tour.getState();
@@ -458,21 +458,17 @@ var Tour = {
             step.onload();
         }
 
-        if (next) {
+        if (state.mode === "test") {
             setTimeout(function () {
-                if (Tour.getState()) {
+                Tour.autoNextStep(state.tour, step);
+                if (next && Tour.getState()) {
                     Tour.waitNextStep();
                 }
-                if (state.mode === "test") {
-                    setTimeout(function(){
-                        Tour.autoNextStep(state.tour, step);
-                    }, Tour.defaultDelay);
-                }
-            }, next.wait || 0);
-        } else {
-            setTimeout(function(){
-                Tour.autoNextStep(state.tour, step);
-            }, Tour.defaultDelay);
+            }, step.wait || Tour.defaultDelay);
+        } else if (next) {
+            setTimeout(Tour.waitNextStep, next.wait || 0);
+        }
+        if (!next) {
             Tour.endTour();
         }
     },
@@ -544,7 +540,7 @@ var Tour = {
             
             }
         }
-        Tour.testtimer = setTimeout(autoStep, 100);
+        Tour.testtimer = setTimeout(autoStep, 0);
     },
     autoDragAndDropSnippet: function (selector) {
         var $thumbnail = $(selector).first();
index 22ec7f1..36050d6 100644 (file)
@@ -207,10 +207,11 @@ class Website(openerp.addons.web.controllers.main.Home):
         return request.redirect(redirect)
 
     @http.route('/website/customize_template_get', type='json', auth='user', website=True)
-    def customize_template_get(self, xml_id, full=False):
+    def customize_template_get(self, xml_id, full=False, bundles=False):
         """ Lists the templates customizing ``xml_id``. By default, only
         returns optional templates (which can be toggled on and off), if
         ``full=True`` returns all templates customizing ``xml_id``
+        ``bundles=True`` returns also the asset bundles
         """
         imd = request.registry['ir.model.data']
         view_model, view_theme_id = imd.get_object_reference(
@@ -221,7 +222,7 @@ class Website(openerp.addons.web.controllers.main.Home):
         user_groups = set(user.groups_id)
 
         views = request.registry["ir.ui.view"]\
-            ._views_get(request.cr, request.uid, xml_id, context=request.context)
+            ._views_get(request.cr, request.uid, xml_id, bundles=bundles, context=request.context)
         done = set()
         result = []
         for v in views:
index ad3597b..61de01c 100644 (file)
@@ -37,7 +37,7 @@ class view(osv.osv):
 
     # Returns all views (called and inherited) related to a view
     # Used by translation mechanism, SEO and optional templates
-    def _views_get(self, cr, uid, view_id, options=True, context=None, root=True):
+    def _views_get(self, cr, uid, view_id, options=True, bundles=False, context=None, root=True):
         """ For a given view ``view_id``, should return:
 
         * the view itself
@@ -57,13 +57,16 @@ class view(osv.osv):
         result = [view]
 
         node = etree.fromstring(view.arch)
-        for child in node.xpath("//t[@t-call]"):
+        xpath = "//t[@t-call]"
+        if bundles:
+            xpath += "| //t[@t-call-assets]"
+        for child in node.xpath(xpath):
             try:
-                called_view = self._view_obj(cr, uid, child.get('t-call'), context=context)
+                called_view = self._view_obj(cr, uid, child.get('t-call', child.get('t-call-assets')), context=context)
             except ValueError:
                 continue
             if called_view not in result:
-                result += self._views_get(cr, uid, called_view, options=options, context=context)
+                result += self._views_get(cr, uid, called_view, options=options, bundles=bundles, context=context)
 
         extensions = view.inherit_children_ids
         if not options:
index f789c74..49adbc3 100644 (file)
@@ -23,8 +23,8 @@
 .navbar.navbar-inverse .cke_top {
   background: transparent;
   border: none;
-  -webkit-box-shadow: none;
   -moz-box-shadow: none;
+  -webkit-box-shadow: none;
   box-shadow: none;
   -ms-filter: "alpha(opacity=50)";
 }
   padding: 2px;
   margin: 0;
   z-index: 20000;
-  background: #414141, -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #646060), color-stop(100%, #262626));
-  background: #414141, -webkit-linear-gradient(#646060, #262626);
   background: #414141, -moz-linear-gradient(#646060, #262626);
-  background: #414141, -o-linear-gradient(#646060, #262626);
+  background: #414141, -webkit-linear-gradient(#646060, #262626);
   background: #414141, linear-gradient(#646060, #262626);
-  -webkit-box-sizing: border-box;
   -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
   box-sizing: border-box;
 }
 .oe_website_editorbar li {
@@ -242,8 +240,9 @@ ul.oe_menu_editor .disclose {
 
 /* ---- RTE ---- {{{ */
 .oe_editable .btn, .btn.oe_editable {
-  -webkit-user-select: auto;
   -moz-user-select: auto;
+  -ms-user-select: auto;
+  -webkit-user-select: auto;
   user-select: auto;
   cursor: text !important;
 }
@@ -401,10 +400,8 @@ ul.oe_menu_editor .disclose {
   height: 660px;
   background-color: black;
   border: 2px solid #1c1f1f;
-  -webkit-border-radius: 10px;
   -moz-border-radius: 10px;
-  -ms-border-radius: 10px;
-  -o-border-radius: 10px;
+  -webkit-border-radius: 10px;
   border-radius: 10px;
   margin: auto;
   top: 0;
@@ -427,13 +424,13 @@ ul.oe_menu_editor .disclose {
   color: #1c1f1f;
 }
 .oe_mobile_preview.modal .modal-content .modal-header .close {
-  color: lightgrey;
-  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
+  color: lightgray;
+  filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false);
   opacity: 1;
 }
 .oe_mobile_preview.modal .modal-content .modal-header .close:hover {
   color: #e00101;
-  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
+  filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false);
   opacity: 1;
 }
 .oe_mobile_preview.modal .modal-content .modal-body {
@@ -464,10 +461,8 @@ ul.oe_menu_editor .disclose {
 }
 .oe_seo_configuration .oe_seo_keyword {
   padding: 0.2em 0.4em 0.2em 0.5em;
-  -webkit-border-radius: 0.4em;
   -moz-border-radius: 0.4em;
-  -ms-border-radius: 0.4em;
-  -o-border-radius: 0.4em;
+  -webkit-border-radius: 0.4em;
   border-radius: 0.4em;
 }
 .oe_seo_configuration li.oe_seo_preview_g {
@@ -571,3 +566,8 @@ ul.oe_menu_editor .disclose {
   filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
   opacity: 0;
 }
+
+.oe_include_bundles {
+  font-weight: normal;
+  padding: 0 8px;
+}
index 2922c94..89a60f5 100644 (file)
@@ -490,6 +490,10 @@ $infobar_height: 20px
         z-index: -1000
         +opacity(0)
 
+.oe_include_bundles
+    font-weight: normal
+    padding: 0 8px
+
 // }}}
 
 // vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker:
index 1fb0b93..03c6747 100644 (file)
@@ -70,6 +70,7 @@
         template: 'website.ace_view_editor',
         events: {
             'change #ace-view-list': 'displaySelectedView',
+            'click .js_include_bundles': 'loadTemplates',
             'click button[data-action=save]': 'saveViews',
             'click button[data-action=format]': 'formatXml',
             'click button[data-action=close]': 'close',
             var self = this;
             self.aceEditor = ace.edit(self.$('#ace-view-editor')[0]);
             self.aceEditor.setTheme("ace/theme/monokai");
-            var viewId = $(document.documentElement).data('view-xmlid');
-            openerp.jsonRpc('/website/customize_template_get', 'call', {
-                'xml_id': viewId,
-                'full': true,
-            }).then(function (views) {
-                self.loadViews.call(self, views);
-                self.open.call(self);
-                var curentHash = window.location.hash;
-                var indexOfView = curentHash.indexOf("?view=");
-                if (indexOfView >= 0) {
-                    var viewId = parseInt(curentHash.substring(indexOfView + 6, curentHash.length), 10);
-                    self.$('#ace-view-list').val(viewId).change();
-                } else {
-                    if (views.length >= 2) {
-                        var mainTemplate = views[1];
-                        self.$('#ace-view-list').val(mainTemplate.id).trigger('change');
-                    }
-                    window.location.hash = hash;
-                }
-            });
+            self.loadTemplates();
 
             var $editor = self.$('.ace_editor');
             function resizeEditor (target) {
             resizeEditor(readEditorWidth());
             resizeEditorHeight(this.getParent().$el.outerHeight()+2);
         },
+        loadTemplates: function () {
+            var self = this;
+            var args = {
+                xml_id: $(document.documentElement).data('view-xmlid'),
+                full: true,
+                bundles: this.$('.js_include_bundles')[0].checked
+            };
+            return openerp
+                .jsonRpc('/website/customize_template_get', 'call', args)
+                .then(function (views) {
+                    self.loadViews.call(self, views);
+                    self.open.call(self);
+                    var curentHash = window.location.hash;
+                    var indexOfView = curentHash.indexOf("?view=");
+                    if (indexOfView >= 0) {
+                        var viewId = parseInt(curentHash.substring(indexOfView + 6, curentHash.length), 10);
+                        self.$('#ace-view-list').val(viewId).change();
+                    } else {
+                        if (views.length >= 2) {
+                            var mainTemplate = views[1];
+                            self.$('#ace-view-list').val(mainTemplate.id).trigger('change');
+                        }
+                        window.location.hash = hash;
+                    }
+                });
+        },
         loadViews: function (views) {
-            var $viewList = this.$('#ace-view-list');
+            var $viewList = this.$('#ace-view-list').empty();
             _(this.buildViewGraph(views)).each(function (view) {
                 if (!view.id) { return; }
 
index f618520..84d755a 100644 (file)
        ---------------------------------------------------- */ 
     var templates_def = $.Deferred().resolve();
     website.add_template_file = function(template) {
+        var def = $.Deferred();
         templates_def = templates_def.then(function() {
-            var def = $.Deferred();
             openerp.qweb.add_template(template, function(err) {
                 if (err) {
                     def.reject(err);
             });
             return def;
         });
+        return def;
     };
 
     website.add_template_file('/website/static/src/xml/website.xml');
index 23f3ca0..28b9dd8 100644 (file)
@@ -6,6 +6,9 @@
                 <select id="ace-view-list" class="form-control oe_view_list">
                     <!-- filled in JS -->
                 </select>
+                <label class="oe_include_bundles">
+                    <input type="checkbox" class="js_include_bundles"/> Include Asset Bundles
+                </label>
                 <button data-action="save" type="submit" class="btn btn-success">Save</button>
                 <button data-action="format" type="button" class="btn btn-warning">Format</button>
                 <button data-action="close" type="button" class="btn btn-info">Close</button>
index 302f43d..e76d6ee 100644 (file)
@@ -22,8 +22,8 @@
                             <h3 class="page-header mt16">1. Define Keywords <small>describing your page content</small></h3>
                             <div class="form-horizontal" role="form">
                                 <div class="form-group">
-                                    <label for="seo_page_keywords" class="col-lg-2 control-label">Add keyword:</label>
-                                    <div class="col-lg-3 col-sm-4">
+                                    <label for="seo_page_keywords" class="col-sm-3 control-label">Add keyword:</label>
+                                    <div class="col-sm-4">
                                         <div class="input-group">
                                           <input type="text" name="seo_page_keywords" class="form-control" maxlength="30"/>
                                           <span class="input-group-btn">
                             <h3 class="page-header">2. Reference Your Page <small>using above suggested keywords</small></h3>
                             <div class="form-horizontal mt16" role="form">
                                 <div class="form-group">
-                                    <label for="seo_page_title" class="col-lg-2 control-label">Title</label>
-                                    <div class="col-lg-8">
+                                    <label for="seo_page_title" class="col-sm-2 control-label">Title</label>
+                                    <div class="col-sm-8">
                                         <input type="text" name="seo_page_title" class="form-control" maxlength="70" size="70"/>
                                     </div>
                                 </div>
                                 <div class="form-group">
-                                    <label for="seo_page_description" class="col-lg-2 control-label">Description</label>
-                                    <div class="col-lg-8">
+                                    <label for="seo_page_description" class="col-sm-2 control-label">Description</label>
+                                    <div class="col-sm-8">
                                         <textarea name="seo_page_description" class="form-control" rows="3" cols="70" t-att-maxlength="widget.maxDescriptionSize"/>
                                     </div>
                                 </div>
index afc2b59..8924c07 100644 (file)
                                     <li class="dropdown" t-ignore="true" t-if="website.user_id != user_id">
                                         <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                                             <b>
-                                                <span t-esc="user_id.name"/>
+                                                <span t-esc="(len(user_id.name)&gt;25) and (user_id.name[:23]+'...') or user_id.name"/>
                                                 <span class="caret"></span>
                                             </b>
                                         </a>
index 426016f..9dc0c14 100644 (file)
@@ -4,8 +4,8 @@
     'use strict';
     
     var website = openerp.website,
-    qweb = openerp.qweb;
-    website.add_template_file('/website_blog/static/src/xml/website_blog.inline.discussion.xml');
+        qweb = openerp.qweb;
+
     website.blog_discussion = openerp.Class.extend({
         init: function(options) {
             var self = this ;
                 public_user: false,
             };
             self.settings = $.extend({}, defaults, options);
-            self.do_render(self);
+
+            // TODO: bundlify qweb templates
+            website.add_template_file('/website_blog/static/src/xml/website_blog.inline.discussion.xml').then(function () {
+                self.do_render(self);
+            });
         },
         do_render: function(data) {
             var self = this;
index cdee16d..39699f6 100644 (file)
@@ -2,13 +2,23 @@
 <openerp>
 <data>
 
-<template id="assets_editor" inherit_id="website.assets_editor" name="Blog Editor" groups="base.group_document_user">
+<template id="assets_editor" inherit_id="website.assets_editor" name="Blog Editor">  <!-- groups="base.group_document_user" -->
     <xpath expr="." position="inside">
         <script type="text/javascript" src="/website_blog/static/src/js/website_blog.editor.js"></script>
         <script type="text/javascript" src="/website_blog/static/src/js/website.tour.blog.js"></script>
     </xpath>
 </template>
 
+<template id="assets_frontend" inherit_id="website.assets_frontend" name="Blog Front-end assets">
+    <xpath expr="." position="inside">
+        <link rel='stylesheet' href='/website_blog/static/src/css/website_blog.css'/>
+
+        <script type="text/javascript" src="/website_blog/static/lib/contentshare.js"/>
+        <script type="text/javascript" src="/website_blog/static/src/js/website_blog.inline.discussion.js"></script>
+        <script type="text/javascript" src="/website_blog/static/src/js/website_blog.js"/>
+    </xpath>
+</template>
+
 <!-- Layout add nav and footer -->
 <template id="header_footer_custom" inherit_id="website.footer_default" name="Footer News Blog Link">
     <xpath expr="//div[@id='info']/ul" position="inside">
 <!-- Page -->
 <template id="index" name="Blog Navigation">
     <t t-call="website.layout">
-        <t t-set="head">
-            <link rel='stylesheet' href='/website_blog/static/src/css/website_blog.css'/>
-            <script type="text/javascript" src="/website_blog/static/src/js/website_blog.inline.discussion.js"></script>
-            <script type="text/javascript" src="/website_blog/static/src/js/website_blog.js"/>
-            <script type="text/javascript" src="/website_blog/static/lib/contentshare.js"/>
-        </t>
         <div id="wrap" class="js_blog">
             <t t-raw="0"/>
         </div>
index 44e45e2..1287c4f 100644 (file)
@@ -6,3 +6,6 @@ class res_partner_grade(osv.osv):
     _columns = {
         'website_published': fields.boolean('Published On Website', copy=False),
     }
+    _defaults = {
+          'website_published': True,
+    }
index f6c01f8..9e0ba46 100644 (file)
@@ -1,4 +1,4 @@
-.ribbon-wrapper {
+.oe_sponsor.ribbon-wrapper {
   width: 60px;
   height: 60px;
   z-index: 5;
@@ -8,7 +8,7 @@
   right: 0;
 }
 
-.ribbon {
+.oe_sponsor.ribbon {
   font: bold 13px Sans-Serif;
   color: #404040;
   text-align: center;
@@ -26,7 +26,7 @@
   box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.3);
 }
 
-.ribbon.ribbon_Gold {
+.oe_sponsor.ribbon.ribbon_Gold {
   background-color: #FDE21B;
   background-image: -webkit-gradient(linear, left top, left bottom, from(#E9CE0C), to(#FDE21B));
   background-image: -webkit-linear-gradient(top, #E9CE0C, #FDE21B);
@@ -35,7 +35,7 @@
   background-image: -o-linear-gradient(top, #E9CE0C, #FDE21B);
 }
 
-.ribbon.ribbon_Silver {
+.oe_sponsor.ribbon.ribbon_Silver {
   background-color: #CCCCCC;
   background-image: -webkit-gradient(linear, left top, left bottom, from(#BBBBBB), to(#CCCCCC));
   background-image: -webkit-linear-gradient(top, #BBBBBB, #CCCCCC);
@@ -44,7 +44,7 @@
   background-image: -o-linear-gradient(top, #BBBBBB, #CCCCCC);
 }
 
-.ribbon.ribbon_Bronze {
+.oe_sponsor.ribbon.ribbon_Bronze {
   background-color: #DB9141;
   background-image: -webkit-gradient(linear, left top, left bottom, from(#C2792A), to(#DB9141));
   background-image: -webkit-linear-gradient(top, #C2792A, #DB9141);
index 8093b9e..494d75b 100644 (file)
@@ -1,11 +1,5 @@
 $(document).ready(function() {
 
-    jQuery.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) {
-        return function( elem ) {
-            return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
-        };
-    });
-
     $("#event_track_search").bind('keyup', function(e){
         var change_text = $(this).val();
         $('.event_track').removeClass('invisible');
index 05da470..b9ccee0 100644 (file)
@@ -2,20 +2,20 @@
 <openerp>
 <data>
 
-<template name="Sponsors" id="event_sponsor" optional="enabled" inherit_id="website_event.layout">
-    <xpath expr="//t[@t-call='website.layout']" position="inside">
-        <t t-set="head">
-            <link rel='stylesheet' href='/website_event_track/static/src/css/website_event_track.css'/>
-            <t t-raw="head or ''"/>
-        </t>
+<template id="assets_frontend" inherit_id="website.assets_frontend" name="Website Event Track Assets">
+    <xpath expr="." position="inside">
+        <script type="text/javascript" src="/website_event_track/static/src/js/website_event_track.js"></script>
     </xpath>
+</template>
+
+<template name="Sponsors" id="event_sponsor" optional="enabled" inherit_id="website_event.layout">
     <xpath expr="//div[@id='wrap']" position="inside">
         <div class="container mt32 mb16 hidden-print" t-if="event.sponsor_ids">
             <section data-snippet-id="title">
                 <h2 class="text-center mb32">Our Sponsors</h2>
             </section>
             <div class="row">
-                <div t-attf-class="col-md-#{(len(event.sponsor_ids) > 6) and 2 or (12/ len(event.sponsor_ids))} text-center mb16" t-foreach="event.sponsor_ids" t-as="sponsor">
+                <div t-attf-class="col-md-#{(len(event.sponsor_ids) > 6) and 2 or (12/ len(event.sponsor_ids))} text-center mb16 oe_sponsor" t-foreach="event.sponsor_ids" t-as="sponsor">
                     <t t-if="sponsor.url">
                         <a t-att-href="sponsor.url" style="position: relative; display: inline-block;">
                             <span t-field="sponsor.image_medium"
 
 <template id="agenda">
     <t t-call="website_event.layout">
-        <t t-set="head">
-          <script type="text/javascript" src="/website_event_track/static/src/js/website_event_track.js"></script>
-          <t t-raw="head or ''"/>
-        </t>
         <section class="container">
             <h1 class="text-center" t-field="event.name"/>
             <div class="form-inline pull-right">
index 2b28eb4..abb371e 100644 (file)
@@ -315,6 +315,9 @@ class Post(osv.Model):
         create_context = dict(context, mail_create_nolog=True)
         post_id = super(Post, self).create(cr, uid, vals, context=create_context)
         post = self.browse(cr, SUPERUSER_ID, post_id, context=context)  # SUPERUSER_ID to avoid read access rights issues when creating
+        # deleted or closed questions
+        if post.parent_id and (post.parent_id.state == 'close' or post.parent_id.active == False):
+            osv.except_osv(_('Error !'), _('Posting answer on [Deleted] or [Closed] question is prohibited'))
         # karma-based access
         if post.parent_id and not post.can_ask:
             raise KarmaError('Not enough karma to create a new question')
index 73d82e1..288cddb 100644 (file)
             </field>
         </record>
 
-        <!-- Update user prefrence form!-->
-        <record id="view_users_form_simple_modif_forum" model="ir.ui.view">
-            <field name="name">res.users.preferences.form</field>
-            <field name="model">res.users</field>
-            <field name="inherit_id" ref="base.view_users_form_simple_modif"/>
-            <field name="arch" type="xml">
-                <group name="preferences" position="before">
-                    <div style="margin-top: 19px;">
-                        <field name="website_published" class="pull-right" widget="website_button"/>
-                    </div>
-                </group>
-            </field>
-        </record>
-
     </data>
 </openerp>
index db054f8..761ef8e 100644 (file)
@@ -3,8 +3,7 @@
     <data>
 
 <!-- Editor custo -->
-<template id="editor_head" inherit_id="website.editor_head"
-    name="Event Editor">
+<template id="assets_editor" inherit_id="website.assets_editor" name="Forum Editor Assets">
     <xpath expr="." position="inside">
         <link rel='stylesheet' href="/website_forum/static/src/css/website_forum.css"/>
 
     </xpath>
 </template>
 
+<template id="assets_forum" name="Forum Assets">
+    <link rel='stylesheet' href="/web/static/lib/jquery.textext/jquery.textext.css"/>
+    <link rel='stylesheet' href='/website_forum/static/src/css/website_forum.css'/>
+
+    <script type="text/javascript" src="/website_forum/static/src/js/website_forum.js"/>
+    <script type="text/javascript" src="/web/static/lib/jquery.textext/jquery.textext.js"/>
+    <script type="text/javascript" src="/web/static/lib/ckeditor/ckeditor.js"/>
+    <script type="text/javascript">
+        CKEDITOR.config.toolbar = [['Bold','Italic','Underline','Strike'],['NumberedList','BulletedList', 'Blockquote']
+        ,['Outdent','Indent','Link','Unlink','Image'],] ;
+    </script>
+</template>
+
 <!-- Layout add nav and footer -->
 <template id="header_footer_custom" inherit_id="website.footer_default"
     name="Footer Questions Link">
 <template id="header" name="Forum Index">
     <t t-call="website.layout">
         <t t-set="head">
-            <link rel='stylesheet' href="/web/static/lib/jquery.textext/jquery.textext.css"/>
-            <link rel='stylesheet' href='/website_forum/static/src/css/website_forum.css'/>
-            <script type="text/javascript" src="/website_forum/static/src/js/website_forum.js"/>
-            <script type="text/javascript" src="/web/static/lib/jquery.textext/jquery.textext.js"/>
-            <script type="text/javascript" src="/web/static/lib/ckeditor/ckeditor.js"/>
-            <script type="text/javascript">
-                CKEDITOR.config.toolbar = [['Bold','Italic','Underline','Strike'],['NumberedList','BulletedList', 'Blockquote']
-                ,['Outdent','Indent','Link','Unlink','Image'],] ;
-            </script>
+            <t t-call-assets="website_forum.assets_forum"/>
         </t>
         <div class="container mt16">
             <div class="navbar navbar-default">
 <!-- Edition: ask your question -->
 <template id="ask_question">
     <t t-call="website_forum.header">
-        <h1 class="mt0">Ask your Question</h1>
+        <h1 class="mt0">Ask Your Question</h1>
+        <p>
+            To improve your chance getting an answer:
+        </p>
         <ul>
-            <li> please, try to make your question interesting to others </li>
-            <li> provide enough details and, if possible, give an example </li>
-            <li> be clear and concise, avoid unnecessary introductions (Hi, ... Thanks...) </li>
+            <li>Set a clear, explicit and concise question title
+                (check
+                <a href="#" data-placement="top" data-toggle="popover" data-content="Inventory Date Problem, Task remaining hours, Can you help solve solve my tax computation problem in Canada?" title="Click to get bad question samples">bad examples</a>
+                and
+                <a href="#" data-placement="bottom" data-toggle="popover" data-content="How to create a physical inventory at an anterior date?, How is the 'remaining hours' field computed on tasks?, How to configure TPS and TVQ's canadian taxes?" title="Click to get good question titles">good examples</a>
+                ),
+            </li>
+            <li>Avoid unnecessary introductions (Hi,... Please... Thanks...),</li>
+            <li>Provide enough details and, if possible, give an example.</li>
         </ul>
+        <script type="text/javascript">
+            $(function () {
+                $("[data-toggle='popover']").popover();
+            });
+        </script>
         <form t-attf-action="/forum/#{ slug(forum) }/question/new" method="post" role="form" class="tag_text">
             <input type="text" name="question_name" required="True" t-attf-value="#{question_name}"
-                class="form-control" placeholder="Enter your Question"/>
-            <h5 class="mt20">Please enter a descriptive question (should finish by a '?')</h5>
+                class="form-control mb16" placeholder="Your Question Title..."/>
             <input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
             <textarea name="content" required="True" class="form-control load_editor">
                 <t t-esc="question_content"/>
 <!-- Specific Post Layout -->
 <template id="post_description_full" name="Question Navigation">
     <t t-call="website_forum.header">
-        <div t-attf-class="question #{not question.active and 'alert alert-danger' or ''}">
+        <div t-attf-class="question">
             <div class="col-md-2 hidden-xs text-center">
                 <t t-call="website_forum.vote">
                     <t t-set="post" t-value="question"/>
                         t-attf-class="favourite_question no-decoration fa fa-2x fa-star #{question.user_favourite and 'forum_favourite_question' or ''}"/>
                 </div>
             </div>
-            <div class="col-md-10">
+            <div t-attf-class="col-md-10 #{not question.active and 'alert alert-danger' or ''}">
                 <h1 class="mt0">
                     <a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }" t-field="question.name"/>
                     <span t-if="not question.active"><b> [Deleted]</b></span>
                 </div>
             </div>
         </div>
-        <div t-if="not question.uid_has_answered">
+        <div t-if="not question.uid_has_answered and question.state != 'close' and question.active != False">
             <t t-call="website_forum.post_answer"/>
         </div>
         <div t-if="question.uid_has_answered" class="mb16">
                         class="close comment_delete">&amp;times;</button>
                     <span t-field="message.body"/>
                     <t t-set="required_karma" t-value="message.author_id.id == user.partner_id.id and object.forum_id.karma_comment_convert_own or object.forum_id.karma_comment_convert_all"/>
-                    <t t-call="website_forum.link_button">
-                        <t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(object) + '/comment/' + slug(message) + '/convert_to_answer'"/>
-                        <t t-set="label" t-value="'Convert as an answer'"/>
-                        <t t-set="karma" t-value="user.karma&lt;required_karma and required_karma or 0"/>
-                        <t t-set="classes" t-value="'fa-magic pull-right'"/>
+                    <t t-if="(object.parent_id and object.parent_id.state != 'close' and object.parent_id.active != False) or (not object.parent_id and object.state != 'close' and object.active != False)">
+                        <t t-set="allow_post_comment" t-value="True" />
+                    </t>
+                    <t t-if="allow_post_comment">
+                        <t t-call="website_forum.link_button" >
+                            <t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(object) + '/comment/' + slug(message) + '/convert_to_answer'"/>
+                            <t t-set="label" t-value="'Convert as an answer'"/>
+                            <t t-set="karma" t-value="user.karma&lt;required_karma and required_karma or 0"/>
+                            <t t-set="classes" t-value="'fa-magic pull-right'"/>
+                        </t>
                     </t>
                     <a t-attf-href="/forum/#{slug(forum)}/partner/#{message.author_id.id}"
                         t-field="message.author_id" t-field-options='{"widget": "contact", "country_image": true, "fields": ["name", "country_id"]}'
index 2162e6b..052f20e 100644 (file)
@@ -10,9 +10,21 @@ class WebsiteResPartner(osv.Model):
     def _get_ids(self, cr, uid, ids, flds, args, context=None):
         return {i: i for i in ids}
 
+    def _set_private(self, cr, uid, ids, field_name, value, arg, context=None):
+        return self.write(cr, uid, ids, {'website_published': not value}, context=context)
+
+    def _get_private(self, cr, uid, ids, field_name, arg, context=None):
+        return dict((rec.id, not rec.website_published) for rec in self.browse(cr, uid, ids, context=context))
+
+    def _search_private(self, cr, uid, obj, name, args, context=None):
+        return [('website_published', '=', not args[0][2])]
+
     _columns = {
         'website_published': fields.boolean(
             'Publish', help="Publish on the website", copy=False),
+        'website_private': fields.function(
+            _get_private, fnct_inv=_set_private, fnct_search=_search_private,
+            type='boolean', string='Private Profile'),
         'website_description': fields.html(
             'Website Partner Full Description'
         ),
index 818b783..073539f 100644 (file)
@@ -11,7 +11,7 @@
             <field name="arch" type="xml">
                 <data>
                     <field name="active" position="after">
-                        <field name="website_published"/>
+                        <field name="website_private"/>
                     </field>
                 </data>
             </field>
index 8fab07f..af90422 100644 (file)
@@ -38,6 +38,7 @@ class sale_quote(http.Controller):
         # only if he knows the private token
         order = request.registry.get('sale.order').browse(request.cr, token and SUPERUSER_ID or request.uid, order_id)
         now = time.strftime('%Y-%m-%d')
+        dummy, action = request.registry.get('ir.model.data').get_object_reference(request.cr, request.uid, 'sale', 'action_quotations')
         if token:
             if token != order.access_token:
                 return request.website.render('website.404')
@@ -54,7 +55,8 @@ class sale_quote(http.Controller):
             'message': message and int(message) or False,
             'option': bool(filter(lambda x: not x.line_id, order.options)),
             'order_valid': (not order.validity_date) or (now <= order.validity_date),
-            'days_valid': max(days, 0)
+            'days_valid': max(days, 0),
+            'action': action
         }
         return request.website.render('website_quote.so_quotation', values)
 
index 0415e24..a23f087 100644 (file)
                           <em t-esc="quotation.name"/>
                           <small t-field="quotation.state"/>
                           <div groups="base.group_website_publisher" t-ignore="true" class="pull-right css_editable_mode_hidden">
-                              <a class="btn btn-info hidden-print" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s' % (quotation._name, quotation.id)">Update Quote</a>
+                              <a class="btn btn-info hidden-print" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s&amp;action=%s&amp;view_type=form' % (quotation._name, quotation.id, action)">Update Quote</a>
                           </div>
                       </h1>
 
                       </div>
 
                       <a id="offer"/>
-                      <div t-field="quotation.website_description"/>
+                      <div t-field="quotation.website_description" class="oe_no_empty"/>
 
                       <t t-foreach="quotation.order_line" t-as="line">
                           <a t-att-id="line.id"/>
-                          <div t-field="line.website_description"/>
+                          <div t-field="line.website_description" class="oe_no_empty"/>
                       </t>
     
                       <div class="oe_structure"/>
                                 table of content automatically.
                             </p>
                         </div>
-                        <div id="template_introduction" t-field="template.website_description"/>
+                        <div id="template_introduction" t-field="template.website_description" class="oe_no_empty"/>
                         <t t-foreach="template.quote_line" t-as="line">
                             <div class="alert alert-info alert-dismissable" t-ignore="True">
                                 <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
                                 this content will appear on the quotation only if this
                                 product is put on the quote.
                             </div>
-                            <div t-field="line.website_description"/>
+                            <div t-field="line.website_description" class="oe_no_empty"/>
                         </t>
                         <t t-foreach="template.options" t-as="option_line">
                             <div class="alert alert-info alert-dismissable" t-ignore="True">
                                 this content will appear on the quotation only if this
                                 product is used in the quote.
                             </div>
-                            <div t-field="option_line.website_description"/>
+                            <div t-field="option_line.website_description" class="oe_no_empty"/>
                         </t>
                         <section id="terms" class="container" t-if="template.note">
                             <h1 class="page-header" t-ignore="True">Terms &amp; Conditions</h1>
index c3af83a..6c87ecf 100644 (file)
@@ -6,8 +6,8 @@
             <field name="model">sale.order</field>
             <field name="inherit_id" ref="sale.view_order_form"/>
             <field name="arch" type="xml">
-                <xpath expr="//header/button[@name='invoice_corrected']" position="after">
-                    <button name="open_quotation" string="View Quotation" type="object"
+                <xpath expr="//header/button[@name='action_button_confirm']" position="before">
+                    <button name="open_quotation" string="Preview Quotation" type="object"
                         attrs="{'invisible': [('template_id','=',False)]}"/>
                 </xpath>
                 <xpath expr="//page[@string='Order Lines']" position="after">
index 48d9c24..12c5dff 100644 (file)
@@ -887,7 +887,7 @@ class Contact(orm.AbstractModel):
 
         val = {
             'name': value.split("\n")[0],
-            'address': escape("\n".join(value.split("\n")[1:])),
+            'address': escape("\n".join(value.split("\n")[1:])).strip(),
             'phone': field_browse.phone,
             'mobile': field_browse.mobile,
             'fax': field_browse.fax,
@@ -1005,18 +1005,9 @@ class AssetNotFound(AssetError):
     pass
 
 class AssetsBundle(object):
-    # Sass installation:
-    #
-    #       sudo gem install sass compass bootstrap-sass
-    #
-    # If the following error is encountered:
-    #       'ERROR: Cannot load compass.'
-    # Use this:
-    #       sudo gem install compass --pre
-    cmd_sass = ['sass', '--stdin', '-t', 'compressed', '--unix-newlines', '--compass', '-r', 'bootstrap-sass']
     cache = openerp.tools.lru.LRU(32)
     rx_css_import = re.compile("(@import[^;{]+;?)", re.M)
-    rx_sass_import = re.compile("""(@import\s?['"]([^'"]+)['"])""")
+    rx_preprocess_imports = re.compile("""(@import\s?['"]([^'"]+)['"](;?))""")
     rx_css_split = re.compile("\/\*\! ([a-f0-9-]+) \*\/")
 
     def __init__(self, xmlid, debug=False, cr=None, uid=None, context=None, registry=None):
@@ -1048,12 +1039,16 @@ class AssetsBundle(object):
                 media = el.get('media')
                 if el.tag == 'style':
                     if atype == 'text/sass' or src.endswith('.sass'):
-                        self.stylesheets.append(SassAsset(self, inline=el.text, media=media))
+                        self.stylesheets.append(SassStylesheetAsset(self, inline=el.text, media=media))
+                    elif atype == 'text/less' or src.endswith('.less'):
+                        self.stylesheets.append(LessStylesheetAsset(self, inline=el.text, media=media))
                     else:
                         self.stylesheets.append(StylesheetAsset(self, inline=el.text, media=media))
                 elif el.tag == 'link' and el.get('rel') == 'stylesheet' and self.can_aggregate(href):
                     if href.endswith('.sass') or atype == 'text/sass':
-                        self.stylesheets.append(SassAsset(self, url=href, media=media))
+                        self.stylesheets.append(SassStylesheetAsset(self, url=href, media=media))
+                    elif href.endswith('.less') or atype == 'text/less':
+                        self.stylesheets.append(LessStylesheetAsset(self, url=href, media=media))
                     else:
                         self.stylesheets.append(StylesheetAsset(self, url=href, media=media))
                 elif el.tag == 'script' and not src:
@@ -1078,7 +1073,7 @@ class AssetsBundle(object):
         response = []
         if debug:
             if css and self.stylesheets:
-                self.compile_sass()
+                self.preprocess_css()
                 for style in self.stylesheets:
                     response.append(style.to_html())
             if js:
@@ -1129,7 +1124,7 @@ class AssetsBundle(object):
             # Invalidate cache on version mismach
             self.cache.pop(key)
         if key not in self.cache:
-            self.compile_sass()
+            self.preprocess_css()
             content = '\n'.join(asset.minify() for asset in self.stylesheets)
 
             if self.css_errors:
@@ -1164,39 +1159,41 @@ class AssetsBundle(object):
             }
         """ % message.replace('"', '\\"')
 
-    def compile_sass(self):
+    def preprocess_css(self):
         """
-            Checks if the bundle contains any sass content, then compiles it to css.
-            Css compilation is done at the bundle level and not in the assets
-            because they are potentially interdependant.
+            Checks if the bundle contains any sass/less content, then compiles it to css.
         """
-        sass = [asset for asset in self.stylesheets if isinstance(asset, SassAsset)]
-        if not sass:
+        to_preprocess = [asset for asset in self.stylesheets if isinstance(asset, PreprocessedCSS)]
+        if not to_preprocess:
             return
-        source = '\n'.join([asset.get_source() for asset in sass])
+        to_compile = [asset for asset in to_preprocess if type(asset) == type(to_preprocess[0])]
+        if len(to_preprocess) != len(to_compile):
+            self.css_errors.append("You can't mix css preprocessors languages in the same bundle. (%s)" % self.xmlid)
+        command = to_compile[0].get_command()
+        source = '\n'.join([asset.get_source() for asset in to_compile])
 
         # move up all @import rules to the top and exclude file imports
         imports = []
         def push(matchobj):
             ref = matchobj.group(2)
-            line = '@import "%s"' % ref
+            line = '@import "%s"%s' % (ref, matchobj.group(3))
             if '.' not in ref and line not in imports and not ref.startswith(('.', '/', '~')):
                 imports.append(line)
             return ''
-        source = re.sub(self.rx_sass_import, push, source)
+        source = re.sub(self.rx_preprocess_imports, push, source)
         imports.append(source)
         source = u'\n'.join(imports)
 
         try:
-            compiler = Popen(self.cmd_sass, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+            compiler = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
         except Exception:
-            msg = "Could not find 'sass' program needed to compile sass/scss files"
+            msg = "Could not execute command %r" % command[0]
             _logger.error(msg)
             self.css_errors.append(msg)
             return
         result = compiler.communicate(input=source.encode('utf-8'))
         if compiler.returncode:
-            error = self.get_sass_error(''.join(result), source=source)
+            error = self.get_preprocessor_error(''.join(result), source=source)
             _logger.warning(error)
             self.css_errors.append(error)
             return
@@ -1204,15 +1201,18 @@ class AssetsBundle(object):
         fragments = self.rx_css_split.split(compiled)[1:]
         while fragments:
             asset_id = fragments.pop(0)
-            asset = next(asset for asset in sass if asset.id == asset_id)
+            asset = next(asset for asset in to_compile if asset.id == asset_id)
             asset._content = fragments.pop(0)
 
-    def get_sass_error(self, stderr, source=None):
+    def get_preprocessor_error(self, stderr, source=None):
         # TODO: try to find out which asset the error belongs to
         error = stderr.split('Load paths')[0].replace('  Use --trace for backtrace.', '')
+        if 'Cannot load compass' in error:
+            error += "Maybe you should install the compass gem using this extra argument:\n\n" \
+                     "    $ sudo gem install compass --pre\n"
         error += "This error occured while compiling the bundle '%s' containing:" % self.xmlid
         for asset in self.stylesheets:
-            if isinstance(asset, SassAsset):
+            if isinstance(asset, PreprocessedCSS):
                 error += '\n    - %s' % (asset.url if asset.url else '<inline sass>')
         return error
 
@@ -1377,11 +1377,8 @@ class StylesheetAsset(WebAsset):
         else:
             return '<style type="text/css"%s>%s</style>' % (media, self.with_header())
 
-class SassAsset(StylesheetAsset):
+class PreprocessedCSS(StylesheetAsset):
     html_url = '%s.css'
-    rx_indent = re.compile(r'^( +|\t+)', re.M)
-    indent = None
-    reindent = '    '
 
     def minify(self):
         return self.with_header()
@@ -1403,12 +1400,25 @@ class SassAsset(StylesheetAsset):
                     name=url,
                     url=url,
                 ), context=self.context)
-        return super(SassAsset, self).to_html()
+        return super(PreprocessedCSS, self).to_html()
+
+    def get_source(self):
+        content = self.inline or self._fetch_content()
+        return "/*! %s */\n%s" % (self.id, content)
+
+    def get_command(self):
+        raise NotImplementedError
+
+class SassStylesheetAsset(PreprocessedCSS):
+    rx_indent = re.compile(r'^( +|\t+)', re.M)
+    indent = None
+    reindent = '    '
 
     def get_source(self):
         content = textwrap.dedent(self.inline or self._fetch_content())
 
         def fix_indent(m):
+            # Indentation normalization
             ind = m.group()
             if self.indent is None:
                 self.indent = ind
@@ -1423,6 +1433,16 @@ class SassAsset(StylesheetAsset):
             pass
         return "/*! %s */\n%s" % (self.id, content)
 
+    def get_command(self):
+        return ['sass', '--stdin', '-t', 'compressed', '--unix-newlines', '--compass',
+               '-r', 'bootstrap-sass']
+
+class LessStylesheetAsset(PreprocessedCSS):
+    def get_command(self):
+        webpath = openerp.http.addons_manifest['web']['addons_path']
+        lesspath = os.path.join(webpath, 'web', 'static', 'lib', 'bootstrap', 'less')
+        return ['lessc', '-', '--clean-css', '--no-js', '--no-color', '--include-path=%s' % lesspath]
+
 def rjsmin(script):
     """ Minify js with a clever regex.
     Taken from http://opensource.perlig.de/rjsmin
index 95b8ca1..4eb74cc 100644 (file)
@@ -234,7 +234,7 @@ class res_partner(osv.Model, format_address):
         'title': fields.many2one('res.partner.title', 'Title'),
         'parent_id': fields.many2one('res.partner', 'Related Company', select=True),
         'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts', domain=[('active','=',True)]), # force "active_test" domain to bypass _search() override
-        'ref': fields.char('Contact Reference', select=1),
+        'ref': fields.char('Internal Reference', select=1),
         'lang': fields.selection(_lang_get, 'Language',
             help="If the selected language is loaded in the system, all documents related to this contact will be printed in this language. If not, it will be English."),
         'tz': fields.selection(_tz_get,  'Timezone', size=64,
index 2981449..a453683 100644 (file)
                                 <group>
                                     <field name="ref"/>
                                     <field name="lang"/>
-                                    <field name="date"/>
                                 </group>
-                                <group>
+                            </group>
+                            <group>
+                                <group string="Mailing" name="mailing">
                                     <field name="active"/>
                                 </group>
                             </group>
+                            <group>
+                                <group name="invoicing"/>
+                                <group name="point_of_sale"/>
+                            </group>
                         </page>
                     </notebook>
                 </sheet>
index 31582d9..8a26113 100644 (file)
@@ -52,17 +52,14 @@ def main():
     # Default legacy command
     command = "server"
 
+    # TODO: find a way to properly discover addons subcommands without importing the world
     # Subcommand discovery
     if len(args) and not args[0].startswith("-"):
-        logging.disable(logging.CRITICAL)
-        for m in module.get_modules():
-            m = 'openerp.addons.' + m
-            __import__(m)
-            #try:
-            #except Exception, e:
-            #    raise
-            #    print e
-        logging.disable(logging.NOTSET)
+    #     logging.disable(logging.CRITICAL)
+    #     for m in module.get_modules():
+    #         m = 'openerp.addons.' + m
+    #         __import__(m)
+    #     logging.disable(logging.NOTSET)
         command = args[0]
         args = args[1:]
 
index 5d3be4f..69ceccd 100644 (file)
@@ -30,7 +30,7 @@ RELEASE_LEVELS_DISPLAY = {ALPHA: ALPHA,
 # properly comparable using normal operarors, for example:
 #  (6,1,0,'beta',0) < (6,1,0,'candidate',1) < (6,1,0,'candidate',2)
 #  (6,1,0,'candidate',2) < (6,1,0,'final',0) < (6,1,2,'final',0)
-version_info = (8, 0, 0, RELEASE_CANDIDATE, 1)
+version_info = (9, 0, 0, ALPHA, 1)
 version = '.'.join(map(str, version_info[:2])) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '')
 series = serie = major_version = '.'.join(map(str, version_info[:2]))