From: OCA git bot Date: Thu, 25 Sep 2014 21:42:30 +0000 (+0200) Subject: Merge remote-tracking branch 'odoo/7.0' into 7.0 X-Git-Url: http://git.inspyration.org/?a=commitdiff_plain;h=a32fe04bc555f69f53a83bd73de82c277cb4beec;hp=074c7b6c2516a3c6bd761808edb01be2fbba19bf;p=odoo%2Fodoo.git Merge remote-tracking branch 'odoo/7.0' into 7.0 --- diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..e4fb5ac --- /dev/null +++ b/.coveragerc @@ -0,0 +1,19 @@ +# Config file .coveragerc +# adapt the include for your project + +[report] +include = + */OCA/OCB/addons/* + */OCA/OCB/openerp/* + +omit = + */tests/* + *__init__.py + +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about null context checking + if context is None: diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6e3379a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,27 @@ +language: python + +python: + - "2.7" + +env: + - DATABASE="openerp_test" + +virtualenv: + system_site_packages: true + +install: + - git clone https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools + - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} + - sudo apt-get install python-lxml + - pip install QUnitSuite flake8 coveralls + - pip install -r ${HOME}/maintainer-quality-tools/travis/requirements.txt + +script: +# - travis_run_flake8 + - createdb ${DATABASE} + - ./openerp-server -d ${DATABASE} --addons-path=./openerp/addons,./addons --stop-after-init --init=all + - coverage run ./openerp-server -d ${DATABASE} --addons-path=./openerp/addons,./addons --stop-after-init --init=all --test-enable --log-level=test | tee stdout.log + - if grep -v mail stdout.log | egrep -q "(At least one test failed when loading the modules.|ERROR ${DATABASE})"; then exit 1; fi + +after_success: + coveralls diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0be55c --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +[![Build Status](https://travis-ci.org/OCA/OCB.png?branch=7.0)](https://travis-ci.org/OCA/OCB) +[![Coverage Status](https://coveralls.io/repos/OCA/OCB/badge.png?branch=7.0)](https://coveralls.io/r/OCA/OCB?branch=master) + +[Original readme](README) diff --git a/addons/account/account.py b/addons/account/account.py index a12b5bd..c2fb1a5 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -734,7 +734,7 @@ class account_journal(osv.osv): 'entry_posted': fields.boolean('Skip \'Draft\' State for Manual Entries', help='Check this box if you don\'t want new journal entries to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation. \nNote that journal entries that are automatically created by the system are always skipping that state.'), 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"), 'allow_date':fields.boolean('Check Date in Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'), - + 'active': fields.boolean('Active', select=True), 'profit_account_id' : fields.many2one('account.account', 'Profit Account'), 'loss_account_id' : fields.many2one('account.account', 'Loss Account'), 'internal_account_id' : fields.many2one('account.account', 'Internal Transfers Account', select=1), @@ -746,6 +746,7 @@ class account_journal(osv.osv): 'with_last_closing_balance' : False, 'user_id': lambda self, cr, uid, context: uid, 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id, + 'active': True, } _sql_constraints = [ ('code_company_uniq', 'unique (code, company_id)', 'The code of the journal must be unique per company !'), @@ -1667,8 +1668,40 @@ class account_move_reconcile(osv.osv): return False return True + # To reconcile journal items must have same account + def _check_same_account(self,cr,uid,ids,context=None): + for rec_line in self.browse(cr, uid, ids, context=context): + move_lines = [] + if not rec_line.opening_reconciliation: + if rec_line.line_id: + first_account = rec_line.line_id[0].account_id.id + move_lines = rec_line.line_id + elif rec_line.line_partial_ids: + first_account = rec_line.line_partial_ids[0].account_id.id + move_lines = rec_line.line_partial_ids + for line in move_lines: + if line.account_id.id != first_account: + return False + return True + + # To reconcile allow reconcilation must be True in account + def _check_allow_reconcile(self, cr, uid, ids, context=None): + for move_line in self.browse(cr ,uid ,ids ,context=context): + lines = [] + if not move_line.opening_reconciliation: + if move_line.line_id: + lines = move_line.line_id + elif move_line.line_partial_ids: + lines = move_line.line_partial_ids + for line in lines: + if not line.account_id.reconcile: + return False + return True + _constraints = [ (_check_same_partner, 'You can only reconcile journal items with the same partner.', ['line_id']), + (_check_same_account, 'You can only reconcile journal items with the same account.',['line_id']), + (_check_allow_reconcile,'To reconcile , allow reconcilation must be true in account.',['line_id']), ] def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None): @@ -2704,6 +2737,11 @@ class account_tax_code_template(osv.osv): 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'), 'sign': fields.float('Sign For Parent', required=True), 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any tax related to this tax Code to appear on invoices."), + 'sequence': fields.integer( + 'Sequence', help=( + "Determine the display order in the report 'Accounting " + "\ Reporting \ Generic Reporting \ Taxes \ Taxes Report'"), + ), } _defaults = { @@ -2736,6 +2774,7 @@ class account_tax_code_template(osv.osv): 'parent_id': tax_code_template.parent_id and ((tax_code_template.parent_id.id in tax_code_template_ref) and tax_code_template_ref[tax_code_template.parent_id.id]) or False, 'company_id': company_id, 'sign': tax_code_template.sign, + 'sequence': tax_code_template.sequence, } #check if this tax code already exists rec_list = obj_tax_code.search(cr, uid, [('name', '=', vals['name']),('code', '=', vals['code']),('company_id', '=', vals['company_id'])], context=context) diff --git a/addons/account/account_analytic_line.py b/addons/account/account_analytic_line.py index e620359..740e7cd 100644 --- a/addons/account/account_analytic_line.py +++ b/addons/account/account_analytic_line.py @@ -29,7 +29,7 @@ class account_analytic_line(osv.osv): _columns = { 'product_uom_id': fields.many2one('product.uom', 'Unit of Measure'), 'product_id': fields.many2one('product.product', 'Product'), - 'general_account_id': fields.many2one('account.account', 'General Account', required=True, ondelete='restrict'), + 'general_account_id': fields.many2one('account.account', 'General Account', ondelete='restrict'), 'move_id': fields.many2one('account.move.line', 'Move Line', ondelete='cascade', select=True), 'journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal', required=True, ondelete='restrict', select=True), 'code': fields.char('Code', size=8), diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 1e4bc7a..c7c5ae4 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -522,8 +522,8 @@ class account_invoice(osv.osv): acc_id = p.property_account_payable.id partner_payment_term = p.property_supplier_payment_term and p.property_supplier_payment_term.id or False fiscal_position = p.property_account_position and p.property_account_position.id or False - if p.bank_ids: - bank_id = p.bank_ids[0].id + if p.commercial_partner_id.bank_ids: + bank_id = p.commercial_partner_id.bank_ids[0].id result = {'value': { 'account_id': acc_id, @@ -1024,7 +1024,7 @@ class account_invoice(osv.osv): line = self.finalize_invoice_move_lines(cr, uid, inv, line) move = { - 'ref': inv.reference and inv.reference or inv.name, + 'ref': inv.reference or inv.supplier_invoice_number or inv.name, 'line_id': line, 'journal_id': journal_id, 'date': inv.date_invoice, @@ -1113,6 +1113,23 @@ class account_invoice(osv.osv): (ref, move_id)) return True + def action_proforma(self, cr, uid, ids, context=None): + """ + Check if all taxes are present with the correct base amount + on creating a proforma invoice. This leaves room for manual + corrections of the tax amount. + """ + if not ids: + return True + if isinstance(ids, (int, long)): + ids = [ids] + ait_obj = self.pool.get('account.invoice.tax') + for inv in self.browse(cr, uid, ids, context=context): + compute_taxes = ait_obj.compute(cr, uid, inv.id, context=context) + self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj) + return self.write( + cr, uid, ids, {'state': 'proforma2'}, context=context) + def action_cancel(self, cr, uid, ids, context=None): if context is None: context = {} @@ -1656,7 +1673,7 @@ class account_invoice_tax(osv.osv): 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True), 'name': fields.char('Tax Description', size=64, required=True), 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]), - 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'), + 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account', readonly=True), 'base': fields.float('Base', digits_compute=dp.get_precision('Account')), 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')), 'manual': fields.boolean('Manual'), @@ -1734,6 +1751,14 @@ class account_invoice_tax(osv.osv): val['account_id'] = tax['account_paid_id'] or line.account_id.id val['account_analytic_id'] = tax['account_analytic_paid_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'], val['account_analytic_id']) if not key in tax_grouped: tax_grouped[key] = val diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index f353877..d4c6198 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -215,7 +215,7 @@
@@ -346,7 +346,7 @@ - +
diff --git a/addons/account/account_invoice_workflow.xml b/addons/account/account_invoice_workflow.xml index 13db193..607dd71 100644 --- a/addons/account/account_invoice_workflow.xml +++ b/addons/account/account_invoice_workflow.xml @@ -17,7 +17,7 @@ proforma2 - write({'state':'proforma2'}) + action_proforma() function diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index cf14d46..a61226e 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -557,8 +557,10 @@ class account_move_line(osv.osv): } _order = "date desc, id desc" _sql_constraints = [ - ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry !'), - ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'), + ('credit_debit1', 'CHECK ((credit * debit) = 0::numeric)', + 'Wrong credit or debit value in accounting entry !'), + ('credit_debit2', 'CHECK ((credit + debit) >= 0::numeric)', + 'Wrong credit or debit value in accounting entry !'), ] def _auto_init(self, cr, context=None): diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index de51be0..93df9e2 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -393,6 +393,7 @@ account.journal
+
diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index 031c902..479e2db 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -65,16 +65,47 @@ class purchase_requisition(osv.osv): 'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order.requisition'), }) return super(purchase_requisition, self).copy(cr, uid, id, default, context) - + + def unlink(self, cr, uid, ids, context=None): + """ + Deletes Requisition and related RFQs + """ + if context is None: context = {} + purchase_obj = self.pool.get('purchase.order') + purchase_ids = self._requisition_procurement_cancel(cr, uid, ids, context=context) + if purchase_ids: + purchase_obj.unlink(cr, uid, purchase_ids, context=context) + return super(purchase_requisition, self).unlink(cr, uid, ids, context=context) + + def _requisition_procurement_cancel(self, cr, uid, ids, context=None): + """ + Cancels procurement order related to requisition + @param ids: requisition ids + @return: Returns purchase orders associated with requisition if any + """ + if context is None: context = {} + purchase_ids = [] + procurement_ids = [] + procurement_obj = self.pool.get('procurement.order') + for requisition in self.browse(cr, uid, ids, context=context): + purchase_ids.extend(purchase.id for purchase in requisition.purchase_ids) + if requisition.state == 'cancel': + continue + procurement_ids.extend(procurement_obj.search(cr, uid, + [('requisition_id', '=', requisition.id)], context=context)) + if procurement_ids: + procurement_obj.action_cancel(cr, uid, procurement_ids) + return purchase_ids + def tender_cancel(self, cr, uid, ids, context=None): + if context is None: context = {} purchase_order_obj = self.pool.get('purchase.order') - for purchase in self.browse(cr, uid, ids, context=context): - for purchase_id in purchase.purchase_ids: - if str(purchase_id.state) in('draft'): - purchase_order_obj.action_cancel(cr,uid,[purchase_id.id]) + purchase_ids = self._requisition_procurement_cancel(cr, uid, ids, context=context) + if purchase_ids: + purchase_order_obj.action_cancel(cr, uid, purchase_ids, context=context) procurement_ids = self.pool['procurement.order'].search(cr, uid, [('requisition_id', 'in', ids)], context=context) self.pool['procurement.order'].action_done(cr, uid, procurement_ids) - return self.write(cr, uid, ids, {'state': 'cancel'}) + return self.write(cr, uid, ids, {'state': 'cancel'}, context=context) def tender_in_progress(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state':'in_progress'} ,context=context) diff --git a/addons/purchase_requisition/test/cancel_purchase_requisition.yml b/addons/purchase_requisition/test/cancel_purchase_requisition.yml index 389c328..64c1d1b 100644 --- a/addons/purchase_requisition/test/cancel_purchase_requisition.yml +++ b/addons/purchase_requisition/test/cancel_purchase_requisition.yml @@ -2,25 +2,25 @@ I cancel requisition. - !python {model: purchase.requisition}: | - self.tender_cancel(cr, uid, [ref("requisition1")]) + self.tender_cancel(cr, uid, [ref("requisition2")]) - I check requisition after cancelled. - - !assert {model: purchase.requisition, id: requisition1}: + !assert {model: purchase.requisition, id: requisition2}: - state == 'cancel' - I reset requisition as "New". - !python {model: purchase.requisition}: | - self.tender_reset(cr, uid, [ref('requisition1')]) + self.tender_reset(cr, uid, [ref('requisition2')]) - I duplicate requisition. - !python {model: purchase.requisition}: | - self.copy(cr, uid, ref('requisition1')) + self.copy(cr, uid, ref('requisition2')) - I delete requisition. - - !python {model: purchase.order}: | - self.unlink(cr, uid, [ref("requisition1")]) + !python {model: purchase.requisition}: | + self.unlink(cr, uid, [ref("requisition2")]) diff --git a/addons/purchase_requisition/test/purchase_requisition.yml b/addons/purchase_requisition/test/purchase_requisition.yml index 38e6678..e10e08d 100644 --- a/addons/purchase_requisition/test/purchase_requisition.yml +++ b/addons/purchase_requisition/test/purchase_requisition.yml @@ -88,3 +88,14 @@ (data, format) = netsvc.LocalService('report.purchase.requisition').create(cr, uid, [ref('purchase_requisition.requisition1')], {}, {}) if tools.config['test_report_directory']: file(os.path.join(tools.config['test_report_directory'], 'purchase_requisition-purchase_requisition_report.'+format), 'wb+').write(data) +- + I check that I cannot cancel the requisision +- + !python {model: purchase.requisition}: | + from openerp.osv.osv import except_osv + try: + self.tender_cancel(cr, uid, [ref("requisition1")]) + except except_osv, exc: + assert exc.args == (u'Unable to cancel this purchase order.', u'First cancel all receptions related to this purchase order.') + else: + assert False, 'tender_cancel should have failed' diff --git a/addons/purchase_requisition/test/purchase_requisition_demo.yml b/addons/purchase_requisition/test/purchase_requisition_demo.yml index 5e441f2..628997e 100644 --- a/addons/purchase_requisition/test/purchase_requisition_demo.yml +++ b/addons/purchase_requisition/test/purchase_requisition_demo.yml @@ -7,4 +7,10 @@ - product_id: product.product_product_9 product_qty: 10.0 product_uom_id: product.product_uom_unit - +- + !record {model: purchase.requisition, id: requisition2}: + exclusive: exclusive + line_ids: + - product_id: product.product_product_13 + product_qty: 10.0 + product_uom_id: product.product_uom_unit diff --git a/addons/report_webkit/report_helper.py b/addons/report_webkit/report_helper.py index 884893a..cf1c447 100644 --- a/addons/report_webkit/report_helper.py +++ b/addons/report_webkit/report_helper.py @@ -4,7 +4,8 @@ # Copyright (c) 2010 Camptocamp SA (http://www.camptocamp.com) # All Right Reserved # -# Author : Nicolas Bessi (Camptocamp) +# Authors : Nicolas Bessi (Camptocamp) +# Yannick Vaucher (Camptocamp) # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential @@ -30,6 +31,8 @@ ############################################################################## from openerp import pooler +from openerp.osv import orm +from tools.translate import _ class WebKitHelper(object): """Set of usefull report helper""" @@ -39,19 +42,19 @@ class WebKitHelper(object): self.uid = uid self.pool = pooler.get_pool(self.cursor.dbname) self.report_id = report_id - - def embed_image(self, type, img, width=0, height=0) : + + def embed_image(self, type, img, width=0, height=0, unit="px"): "Transform a DB image into an embedded HTML image" if width : - width = 'width="%spx"'%(width) + width = 'width: %s%s;'%(width, unit) else : width = ' ' if height : - height = 'height="%spx"'%(height) + height = 'height: %s%s;'%(height, unit) else : height = ' ' - toreturn = ''%( + toreturn = ''%( width, height, type, @@ -59,26 +62,32 @@ class WebKitHelper(object): return toreturn - def get_logo_by_name(self, name): + def get_logo_by_name(self, name, company_id=None): """Return logo by name""" header_obj = self.pool.get('ir.header_img') - header_img_id = header_obj.search( - self.cursor, - self.uid, - [('name','=',name)] - ) + domain = [('name','=',name)] + if company_id: + domain.append(('company_id', '=', company_id)) + header_img_id = header_obj.search(self.cursor, + self.uid, + domain) if not header_img_id : - return u'' + msg = _("No header image named '%s' found.") % name + if company_id: + company_obj = self.pool.get('res.company') + company = company_obj.browse(self.cursor, self.uid, company_id) + msg = _("No header image named '%s' found for company %s.") % (name, company.name) + raise orm.except_orm('Error', msg) if isinstance(header_img_id, list): header_img_id = header_img_id[0] head = header_obj.browse(self.cursor, self.uid, header_img_id) return (head.img, head.type) - def embed_logo_by_name(self, name, width=0, height=0): + def embed_logo_by_name(self, name, width=0, height=0, unit="px", company_id=None): """Return HTML embedded logo by name""" - img, type = self.get_logo_by_name(name) - return self.embed_image(type, img, width, height) + img, type = self.get_logo_by_name(name, company_id=company_id) + return self.embed_image(type, img, width, height, unit) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/report_webkit/webkit_report.py b/addons/report_webkit/webkit_report.py index cccaec7..573550f 100644 --- a/addons/report_webkit/webkit_report.py +++ b/addons/report_webkit/webkit_report.py @@ -245,6 +245,7 @@ class WebKitParser(report_sxw): body_mako_tpl = mako_template(template) helper = WebKitHelper(cursor, uid, report_xml.id, context) if report_xml.precise_mode: + objs = self.parser_instance.localcontext['objects'] for obj in objs: parser_instance.localcontext['objects'] = [obj] try : diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index d389715..1aa4ee7 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -340,7 +340,7 @@ class sale_order(osv.osv): return { 'name': pick_name, 'origin': order.name, - 'date': self.date_to_datetime(cr, uid, order.date_order, context), + 'date': self.date_to_datetime(cr, uid, order.date_confirm, context), 'type': 'out', 'state': 'auto', 'move_type': order.picking_policy, @@ -415,7 +415,7 @@ class sale_order(osv.osv): if line.state == 'done': continue - date_planned = self._get_date_planned(cr, uid, order, line, order.date_order, context=context) + date_planned = self._get_date_planned(cr, uid, order, line, order.date_confirm, context=context) if line.product_id: if line.product_id.type in ('product', 'consu'): diff --git a/addons/sale_stock/sale_stock_view.xml b/addons/sale_stock/sale_stock_view.xml index 0b7249f..0920a90 100644 --- a/addons/sale_stock/sale_stock_view.xml +++ b/addons/sale_stock/sale_stock_view.xml @@ -40,7 +40,7 @@