diff options
| author | Ajay Chauhan (OpenERP) <cha@tinyerp.com> | 2012-10-01 14:47:30 +0530 |
|---|---|---|
| committer | Ajay Chauhan (OpenERP) <cha@tinyerp.com> | 2012-10-01 14:47:30 +0530 |
| commit | d2b51d4e149e8542f77fd2a171d0a4498352be2d (patch) | |
| tree | 0e10ae6b0e694692df4260a65f2b7ac88c9573ca | |
| parent | 826e2b8fdbb267efab4feff67e373e0e0c1c1568 (diff) | |
| parent | 3686a01df372b01dcec9a5438ba9b16256895c5e (diff) | |
[MERGE] merge with lp:openobject-addons
bzr revid: cha@tinyerp.com-20121001091730-q6sbcvj7nu53t91h
169 files changed, 1428 insertions, 930 deletions
diff --git a/addons/account/account.py b/addons/account/account.py index c2e6f3d1221..fbbbfd64537 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -31,7 +31,7 @@ import decimal_precision as dp from tools.translate import _ from tools.float_utils import float_round from openerp import SUPERUSER_ID - +import tools _logger = logging.getLogger(__name__) @@ -227,7 +227,7 @@ class account_account(osv.osv): while pos < len(args): if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]: - args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%') + args[pos] = ('code', '=like', tools.ustr(args[pos][2].replace('%', ''))+'%') if args[pos][0] == 'journal_id': if not args[pos][2]: del args[pos] @@ -682,7 +682,7 @@ class account_journal_view(osv.osv): _name = "account.journal.view" _description = "Journal View" _columns = { - 'name': fields.char('Journal View', size=64, required=True), + 'name': fields.char('Journal View', size=64, required=True, translate=True), 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns') } _order = "name" @@ -1908,7 +1908,7 @@ class account_tax(osv.osv): 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."), 'include_base_amount': fields.boolean('Included in base amount', help="Indicates if the amount of tax must be included in the base amount for the computation of the next taxes"), 'company_id': fields.many2one('res.company', 'Company', required=True), - 'description': fields.char('Tax Code',size=32), + 'description': fields.char('Tax Code'), 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."), 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True) @@ -2518,7 +2518,7 @@ class account_account_template(osv.osv): 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."), 'shortcut': fields.char('Shortcut', size=12), 'note': fields.text('Note'), - 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'), + 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade', domain=[('type','=','view')]), 'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'), 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'), 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."), @@ -2536,20 +2536,6 @@ class account_account_template(osv.osv): (_check_recursion, 'Error!\nYou cannot create recursive account templates.', ['parent_id']), ] - def create(self, cr, uid, vals, context=None): - if 'parent_id' in vals: - parent = self.read(cr, uid, [vals['parent_id']], ['type']) - if parent and parent[0]['type'] != 'view': - raise osv.except_osv(_('Warning!'), _("You may only select a parent account of type 'View'.")) - return super(account_account_template, self).create(cr, uid, vals, context=context) - - def write(self, cr, uid, ids, vals, context=None): - if 'parent_id' in vals: - parent = self.read(cr, uid, [vals['parent_id']], ['type']) - if parent and parent[0]['type'] != 'view': - raise osv.except_osv(_('Warning!'), _("You may only select a parent account of type 'View'.")) - return super(account_account_template, self).write(cr, uid, ids, vals, context=context) - def name_get(self, cr, uid, ids, context=None): if not ids: return [] @@ -2828,7 +2814,7 @@ class account_tax_template(osv.osv): 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."), 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."), 'include_base_amount': fields.boolean('Include in Base Amount', help="Set if the amount of tax must be included in the base amount before computing the next taxes."), - 'description': fields.char('Internal Name', size=32), + 'description': fields.char('Internal Name'), 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,), 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."), } diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index 9889eadf387..b4fc2388a68 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -457,6 +457,8 @@ class account_bank_statement(osv.osv): return res and res[0] or 0.0 def onchange_journal_id(self, cr, uid, statement_id, journal_id, context=None): + if not journal_id: + return {} balance_start = self._compute_balance_end_real(cr, uid, journal_id, context=context) journal_data = self.pool.get('account.journal').read(cr, uid, journal_id, ['default_debit_account_id', 'company_id'], context=context) diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index aff38d71cfe..a1c53eff4b1 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -508,8 +508,10 @@ class account_invoice(osv.osv): if journal_id: journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context) currency_id = journal.currency and journal.currency.id or journal.company_id.currency_id.id + company_id = journal.company_id.id result = {'value': { 'currency_id': currency_id, + 'company_id': company_id, } } return result @@ -1371,7 +1373,10 @@ class account_invoice_line(osv.osv): 'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True) } - def _default_account_id(self, cr, uid, ids, context=None): + def _default_account_id(self, cr, uid, context=None): + # XXX this gets the default account for the user's company, + # it should get the default account for the invoice's company + # however, the invoice's company does not reach this point prop = self.pool.get('ir.property').get(cr, uid, 'property_account_income_categ', 'product.category', context=context) return prop and prop.id or False @@ -1401,7 +1406,7 @@ class account_invoice_line(osv.osv): context = {} company_id = company_id if company_id != None else context.get('company_id',False) context = dict(context) - context.update({'company_id': company_id}) + context.update({'company_id': company_id, 'force_company': company_id}) if not partner_id: raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") ) if not product: @@ -1556,16 +1561,19 @@ class account_invoice_line(osv.osv): def onchange_account_id(self, cr, uid, ids, product_id, partner_id, inv_type, fposition_id, account_id): if not account_id: return {} - taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids + unique_tax_ids = [] fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False - tax_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes) - - product_change_result = self.product_id_change(cr, uid, ids, product_id, False, type=inv_type, - partner_id=partner_id, fposition_id=fposition_id) - unique_tax_ids = set(tax_ids) - if product_change_result and 'value' in product_change_result and 'invoice_line_tax_id' in product_change_result['value']: - unique_tax_ids |= set(product_change_result['value']['invoice_line_tax_id']) - return {'value':{'invoice_line_tax_id': list(unique_tax_ids)}} + account = self.pool.get('account.account').browse(cr, uid, account_id) + if not product_id: + taxes = account.tax_ids + unique_tax_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes) + else: + product_change_result = self.product_id_change(cr, uid, ids, product_id, False, type=inv_type, + partner_id=partner_id, fposition_id=fposition_id, + company_id=account.company_id.id) + if product_change_result and 'value' in product_change_result and 'invoice_line_tax_id' in product_change_result['value']: + unique_tax_ids = product_change_result['value']['invoice_line_tax_id'] + return {'value':{'invoice_line_tax_id': unique_tax_ids}} account_invoice_line() @@ -1650,14 +1658,13 @@ class account_invoice_tax(osv.osv): for line in inv.invoice_line: for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, (line.price_unit* (1-(line.discount or 0.0)/100.0)), line.quantity, line.product_id, inv.partner_id)['taxes']: - tax['price_unit'] = cur_obj.round(cr, uid, cur, tax['price_unit']) val={} val['invoice_id'] = inv.id val['name'] = tax['name'] val['amount'] = tax['amount'] val['manual'] = False val['sequence'] = tax['sequence'] - val['base'] = tax['price_unit'] * line['quantity'] + val['base'] = cur_obj.round(cr, uid, cur, tax['price_unit'] * line['quantity']) if inv.type in ('out_invoice','in_invoice'): val['base_code_id'] = tax['base_code_id'] diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 1c4d83106ca..4a587eee6fe 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -278,6 +278,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -299,8 +300,8 @@ <button name="invoice_open" states="draft" string="Validate" class="oe_highlight"/> <button name="invoice_open" states="proforma2" string="Validate"/> <button name="invoice_proforma2" states="draft" string="PRO-FORMA" groups="account.group_proforma_invoices"/> - <button name="%(action_account_invoice_refund)d" type='action' string='Refund Invoice' states='paid'/> - <button name="invoice_cancel" states="draft,proforma2,sale,open" string="Cancel" groups="base.group_no_one"/> + <button name="%(action_account_invoice_refund)d" type='action' string='Refund Invoice' states='open,proforma2,paid'/> + <button name="invoice_cancel" states="draft,proforma2,open" string="Cancel" groups="base.group_no_one"/> <button name="action_cancel_draft" states="cancel" string="Reset to Draft" type="object"/> <button name='%(action_account_state_open)d' type='action' string='Re-Open' groups="account.group_account_invoice" attrs="{'invisible':['|', ('state','<>','paid'), ('reconciled', '=', True)]}" help="This button only appears when the state of the invoice is 'paid' (showing that it has been fully reconciled) and auto-computed boolean 'reconciled' is False (depicting that it's not the case anymore). In other words, the invoice has been dereconciled and it does not fit anymore the 'paid' state. You should press this button to re-open it and let it continue its normal process after having resolved the eventual exceptions it may have created."/> <!--button name="%(account_invoices)d" string="Print Invoice" type="action" states="open,paid,proforma,sale,proforma2"/--> @@ -437,6 +438,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 5ddbbee74f2..bb1ace992ca 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -562,6 +562,7 @@ class account_move_line(osv.osv): 'journal_id': lambda self, cr, uid, c: c.get('journal_id', False), 'credit': 0.0, 'debit': 0.0, + 'amount_currency': 0.0, 'account_id': lambda self, cr, uid, c: c.get('account_id', False), 'period_id': lambda self, cr, uid, c: c.get('period_id', False), 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.move.line', context=c) @@ -1193,12 +1194,12 @@ class account_move_line(osv.osv): jour_period_obj = self.pool.get('account.journal.period') cr.execute('SELECT state FROM account_journal_period WHERE journal_id = %s AND period_id = %s', (journal_id, period_id)) result = cr.fetchall() + journal = journal_obj.browse(cr, uid, journal_id, context=context) + period = period_obj.browse(cr, uid, period_id, context=context) for (state,) in result: if state == 'done': - raise osv.except_osv(_('Error!'), _('You cannot add/modify entries in a closed journal.')) + raise osv.except_osv(_('Error !'), _('You can not add/modify entries in a closed period %s of journal %s.' % (period.name,journal.name))) if not result: - journal = journal_obj.browse(cr, uid, journal_id, context=context) - period = period_obj.browse(cr, uid, period_id, context=context) jour_period_obj.create(cr, uid, { 'name': (journal.code or journal.name)+':'+(period.name or ''), 'journal_id': journal.id, diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 2c204fdf707..1fbda80a0fe 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -1305,16 +1305,6 @@ Account.Entry Edition --> - <record id="account_move_graph" model="ir.ui.view"> - <field name="name">account.move.graph</field> - <field name="model">account.move</field> - <field name="arch" type="xml"> - <graph string="Account Statistics" type="bar"> - <field name="period_id"/> - <field name="amount" operator="+"/> - </graph> - </field> - </record> <record id="view_move_tree" model="ir.ui.view"> <field name="name">account.move.tree</field> <field name="model">account.move</field> @@ -1338,8 +1328,8 @@ <field name="arch" type="xml"> <form string="Account Entry" version="7.0"> <header> - <button name="button_validate" states="draft" string="Post" type="object" class="oe_highlight"/> - <button name="button_cancel" states="posted" string="Cancel" type="object"/> + <button name="button_validate" states="draft" string="Post" type="object" class="oe_highlight" groups="account.group_account_invoice"/> + <button name="button_cancel" states="posted" string="Cancel" type="object" groups="account.group_account_invoice"/> <field name="state" widget="statusbar"/> </header> <sheet string="Journal Entries" > @@ -1488,7 +1478,7 @@ <field name="name">Journal Entries</field> <field name="res_model">account.move</field> <field name="view_type">form</field> - <field name="view_mode">tree,form,graph</field> + <field name="view_mode">tree,form</field> <field name="view_id" ref="view_move_tree"/> <field name="search_view_id" ref="view_account_move_filter"/> <field name="help" type="html"> @@ -2037,7 +2027,7 @@ src_model="account.journal"/> <act_window - context="{'search_default_reconcile_id':False, 'search_default_partner_id':[active_id], 'default_partner_id': active_id}" + context="{'search_default_unreconciled':True, 'search_default_partner_id':[active_id], 'default_partner_id': active_id}" domain="[('account_id.reconcile', '=', True),('account_id.type', 'in', ['receivable', 'payable'])]" id="act_account_partner_account_move_all" name="Receivables & Payables" @@ -2073,7 +2063,7 @@ id="account_template_folder" name="Templates" parent="menu_finance_accounting" - groups="base.group_multi_company"/> + groups="account.group_account_manager"/> <menuitem id="account_template_taxes" name="Taxes" diff --git a/addons/account/partner.py b/addons/account/partner.py index f2b9f79bb8a..a9b2b99588d 100644 --- a/addons/account/partner.py +++ b/addons/account/partner.py @@ -44,17 +44,17 @@ class account_fiscal_position(osv.osv): return [] if not fposition_id: return map(lambda x: x.id, taxes) - result = [] + result = set() for t in taxes: ok = False for tax in fposition_id.tax_ids: if tax.tax_src_id.id == t.id: if tax.tax_dest_id: - result.append(tax.tax_dest_id.id) + result.add(tax.tax_dest_id.id) ok=True if not ok: - result.append(t.id) - return result + result.add(t.id) + return list(result) def map_account(self, cr, uid, fposition_id, account_id, context=None): if not fposition_id: @@ -77,6 +77,12 @@ class account_fiscal_position_tax(osv.osv): 'tax_dest_id': fields.many2one('account.tax', 'Replacement Tax') } + _sql_constraints = [ + ('tax_src_dest_uniq', + 'unique (position_id,tax_src_id,tax_dest_id)', + 'A tax fiscal position could be defined only once time on same taxes.') + ] + account_fiscal_position_tax() class account_fiscal_position_account(osv.osv): @@ -89,6 +95,12 @@ class account_fiscal_position_account(osv.osv): 'account_dest_id': fields.many2one('account.account', 'Account Destination', domain=[('type','<>','view')], required=True) } + _sql_constraints = [ + ('account_src_dest_uniq', + 'unique (position_id,account_src_id,account_dest_id)', + 'An account fiscal position could be defined only once time on same accounts.') + ] + account_fiscal_position_account() class res_partner(osv.osv): diff --git a/addons/account/partner_view.xml b/addons/account/partner_view.xml index 7ac1efd4f9f..4bee2002ffd 100644 --- a/addons/account/partner_view.xml +++ b/addons/account/partner_view.xml @@ -92,34 +92,7 @@ <field name="debit"/> </group> </group> - <field name="bank_ids"> - <form string="Bank account" version="7.0"> - <group col="4"> - <field name="state"/> - <field name="acc_number"/> - </group> - <group> - <group name="owner" string="Bank Account Owner"> - <field name="partner_id" on_change="onchange_partner_id(partner_id)"/> - <field name="owner_name"/> - <label for="street" string="Address"/> - <div> - <field name="street" placeholder="Street..."/> - <div> - <field name="zip" class="oe_inline" placeholder="ZIP"/> - <field name="city" class="oe_inline" placeholder="City"/> - </div> - <field name="state_id" placeholder="State" options='{"no_open": true}'/> - <field name="country_id" placeholder="Country" options='{"no_open": true}'/> - </div> - </group> - <group name="bank" string="Information About the Bank"> - <field name="bank" on_change="onchange_bank_id(bank)"/> - <field name="bank_name"/> - <field name="bank_bic" placeholder="e.g. GEBABEBB"/> - </group> - </group> - </form> + <field name="bank_ids" context="{'default_partner_id': active_id, 'form_view_ref': 'base.view_partner_bank_form'}"> <tree string="Bank Details"> <field name="sequence" invisible="1"/> <field name="acc_number"/> @@ -141,7 +114,7 @@ context="{'search_default_partner_id':[active_id], 'default_partner_id': active_id}" src_model="res.partner" view_type="form" - view_mode="tree,form,graph,calendar"/> + view_mode="tree,form,calendar"/> </data> </openerp> diff --git a/addons/account/project/project_view.xml b/addons/account/project/project_view.xml index da1a11b9cb6..70db95425b1 100644 --- a/addons/account/project/project_view.xml +++ b/addons/account/project/project_view.xml @@ -73,7 +73,7 @@ <field name="type">ir.actions.act_window</field> <field name="res_model">account.analytic.account</field> <field name="view_type">form</field> - <field name="view_mode">tree,graph,form</field> + <field name="view_mode">tree,form</field> <field name="view_id" ref="view_account_analytic_account_tree"/> <field name="search_view_id" ref="account.view_account_analytic_account_search"/> </record> @@ -114,9 +114,6 @@ </field> </record> - <menuitem groups="analytic.group_analytic_accounting" id="next_id_40" - name="Analytic" parent="account.menu_finance_generic_reporting" - sequence="4"/> <record id="view_account_analytic_line_form" model="ir.ui.view"> <field name="name">account.analytic.line.form</field> @@ -351,15 +348,6 @@ # Reporting # - <record id="action_account_analytic_journal_tree" model="ir.actions.act_window"> - <field name="name">Print Analytic Journals</field> - <field name="res_model">account.analytic.journal</field> - <field name="view_type">tree</field> - <field name="help">To print an analytics (or costs) journal for a given period. The report give code, move name, account number, general amount and analytic amount.</field> - </record> - <menuitem groups="analytic.group_analytic_accounting" - action="action_account_analytic_journal_tree" - id="account_analytic_journal_print" parent="account.next_id_40"/> <record id="view_account_journal_1" model="ir.ui.view"> <field name="name">account.journal.form.1</field> @@ -372,16 +360,5 @@ </field> </record> - <record id="analytic_accounts_graph" model="ir.ui.view"> - <field name="name">analytic.accounts.graph</field> - <field name="model">account.analytic.account</field> - <field name="arch" type="xml"> - <graph string="Analytic Account Statistics" type="bar"> - <field name="complete_name"/> - <field name="balance" operator="+"/> - </graph> - </field> - </record> - </data> </openerp> diff --git a/addons/account/project/wizard/account_analytic_journal_report.py b/addons/account/project/wizard/account_analytic_journal_report.py index 9a2eee020b1..8148a016927 100644 --- a/addons/account/project/wizard/account_analytic_journal_report.py +++ b/addons/account/project/wizard/account_analytic_journal_report.py @@ -29,6 +29,7 @@ class account_analytic_journal_report(osv.osv_memory): _columns = { 'date1': fields.date('Start of period', required=True), 'date2': fields.date('End of period', required=True), + 'analytic_account_journal_id': fields.many2many('account.analytic.journal', 'account_analytic_journal_name', 'journal_line_id', 'journal_print_id', 'Analytic Journals', required=True), } _defaults = { @@ -40,8 +41,15 @@ class account_analytic_journal_report(osv.osv_memory): if context is None: context = {} data = self.read(cr, uid, ids)[0] + ids_list = [] + if context.get('active_id',False): + ids_list.append(context.get('active_id',False)) + else: + record = self.browse(cr,uid,ids[0],context=context) + for analytic_record in record.analytic_account_journal_id: + ids_list.append(analytic_record.id) datas = { - 'ids': context.get('active_ids',[]), + 'ids': ids_list, 'model': 'account.analytic.journal', 'form': data } @@ -50,6 +58,14 @@ class account_analytic_journal_report(osv.osv_memory): 'report_name': 'account.analytic.journal', 'datas': datas, } + + def default_get(self, cr, uid, fields, context=None): + if context is None: + context = {} + res = super(account_analytic_journal_report, self).default_get(cr, uid, fields, context=context) + if 'analytic_account_journal_id' in fields: + res.update({'analytic_account_journal_id': context.get('active_ids',[])}) + return res account_analytic_journal_report() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account/project/wizard/account_analytic_journal_report_view.xml b/addons/account/project/wizard/account_analytic_journal_report_view.xml index e4c4cd314db..b0cb0293270 100644 --- a/addons/account/project/wizard/account_analytic_journal_report_view.xml +++ b/addons/account/project/wizard/account_analytic_journal_report_view.xml @@ -7,15 +7,16 @@ <field name="model">account.analytic.journal.report</field> <field name="arch" type="xml"> <form string="Select Period" version="7.0"> - <header> - <button name="check_report" string="Print" type="object" class="oe_highlight"/> - or - <button string="Cancel" class="oe_link" special="cancel"/> - </header> <group col="4"> <field name="date1"/> <field name="date2"/> + <field name="analytic_account_journal_id" widget="many2many_tags" class="oe_inline" required="1"/> </group> + <footer> + <button name="check_report" string="Print" type="object" class="oe_highlight"/> + or + <button string="Cancel" class="oe_link" special="cancel"/> + </footer> </form> </field> </record> @@ -38,6 +39,13 @@ <field name="key">action</field> <field name="model">account.analytic.journal</field> </record> + + <menuitem groups="analytic.group_analytic_accounting" id="next_id_40" + name="Analytic" parent="account.menu_finance_generic_reporting" + sequence="4"/> + <menuitem groups="analytic.group_analytic_accounting" + action="account.action_account_analytic_journal" + id="account_analytic_journal_print" parent="account.next_id_40"/> </data> </openerp> diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml index 442b51466f8..4b90e0c8ad5 100644 --- a/addons/account/security/account_security.xml +++ b/addons/account/security/account_security.xml @@ -16,7 +16,7 @@ </record> <record id="group_account_manager" model="res.groups"> - <field name="name">Manager</field> + <field name="name">Financial Manager</field> <field name="category_id" ref="base.module_category_accounting_and_finance"/> <field name="implied_ids" eval="[(4, ref('group_account_user'))]"/> </record> diff --git a/addons/account/wizard/account_invoice_refund.py b/addons/account/wizard/account_invoice_refund.py index b7d278b8849..38a22baa9f1 100644 --- a/addons/account/wizard/account_invoice_refund.py +++ b/addons/account/wizard/account_invoice_refund.py @@ -32,11 +32,11 @@ class account_invoice_refund(osv.osv_memory): _name = "account.invoice.refund" _description = "Invoice Refund" _columns = { - 'date': fields.date('Operation Date', help='This date will be used as the invoice date for credit note and period will be chosen accordingly!'), + 'date': fields.date('Date', help='This date will be used as the invoice date for credit note and period will be chosen accordingly!'), 'period': fields.many2one('account.period', 'Force period'), 'journal_id': fields.many2one('account.journal', 'Refund Journal', help='You can select here the journal to use for the credit note that will be created. If you leave that field empty, it will use the same journal as the current invoice.'), - 'description': fields.char('Description', size=128, required=True), - 'filter_refund': fields.selection([('refund', 'Create a draft refund'), ('cancel', 'Cancel: create credit note and reconcile'),('modify', 'Modify: create credit note, reconcile and create a new draft invoice')], "Refund Method", required=True, help='Credit note base on this type. You can not Modify and Cancel if the invoice is already reconciled'), + 'description': fields.char('Reason', size=128, required=True), + 'filter_refund': fields.selection([('refund', 'Create a draft credit note'), ('cancel', 'Cancel: create credit note and reconcile'),('modify', 'Modify: create credit note, reconcile and create a new draft invoice')], "Refund Method", required=True, help='Credit note base on this type. You can not Modify and Cancel if the invoice is already reconciled'), } def _get_journal(self, cr, uid, context=None): diff --git a/addons/account/wizard/account_invoice_refund_view.xml b/addons/account/wizard/account_invoice_refund_view.xml index 78a9b085bf1..3945a6f57e6 100644 --- a/addons/account/wizard/account_invoice_refund_view.xml +++ b/addons/account/wizard/account_invoice_refund_view.xml @@ -7,19 +7,40 @@ <field name="model">account.invoice.refund</field> <field name="arch" type="xml"> <form string="Credit Note" version="7.0"> - <separator string="Credit Note Options"/> - <group col="4"> - <field name="description"/> - <field name="journal_id" widget='selection'/> - <field name="date"/> - <field name="period"/> - <field name="filter_refund"/> + <group> + <group colspan="2"> + <label for="filter_refund"/> + <div> + <field name="filter_refund" class="oe_inline"/> + <p attrs="{'invisible':[('filter_refund','<>','refund')]}" class="oe_grey"> + You will be able to edit and validate this + credit note directly or keep it draft, + waiting for the document to be issued by + your supplier/customer. + </p> + <p attrs="{'invisible':[('filter_refund','<>','cancel')]}" class="oe_grey"> + Use this option if you want to cancel an invoice you should not + have issued. The credit note will be created, validated and reconciled + with the invoice. You will not be able to modify the credit note. + </p> + <p attrs="{'invisible':[('filter_refund','<>','modify')]}" class="oe_grey"> + Use this option if you want to cancel an invoice and create a new + one. The credit note will be created, validated and reconciled + with the current invoice. A new, draft, invoice will be created + so that you can edit it. + </p> + </div> + </group> + <group> + <field name="description"/> + <field name="journal_id" widget='selection'/> + </group><group> + <field name="date"/> + <field name="period"/> + </group> </group> - <label string="Modify Invoice: Cancels the current invoice and creates a new copy of it ready for editing."/> - <label string="Credit Note: Creates the credit note, ready for editing."/> - <label string="Cancel Invoice: Creates the credit note, validate and reconcile it to cancel the current invoice."/> <footer> - <button string='Refund' name="invoice_refund" type="object" class="oe_highlight"/> + <button string='Create Credit Note' name="invoice_refund" type="object" class="oe_highlight"/> or <button string="Cancel" class="oe_link" special="cancel"/> </footer> @@ -28,7 +49,7 @@ </record> <record id="action_account_invoice_refund" model="ir.actions.act_window"> - <field name="name">Refund</field> + <field name="name">Refund Invoice</field> <field name="res_model">account.invoice.refund</field> <field name="view_type">form</field> <field name="view_mode">tree,form</field> diff --git a/addons/account/wizard/account_reconcile.py b/addons/account/wizard/account_reconcile.py index 9fa521c09f7..f3d25b1c8db 100644 --- a/addons/account/wizard/account_reconcile.py +++ b/addons/account/wizard/account_reconcile.py @@ -23,6 +23,7 @@ import time from osv import fields, osv from tools.translate import _ +import decimal_precision as dp class account_move_line_reconcile(osv.osv_memory): """ @@ -32,9 +33,9 @@ class account_move_line_reconcile(osv.osv_memory): _description = 'Account move line reconcile' _columns = { 'trans_nbr': fields.integer('# of Transaction', readonly=True), - 'credit': fields.float('Credit amount', readonly=True), - 'debit': fields.float('Debit amount', readonly=True), - 'writeoff': fields.float('Write-Off amount', readonly=True), + 'credit': fields.float('Credit amount', readonly=True, digits_compute=dp.get_precision('Account')), + 'debit': fields.float('Debit amount', readonly=True, digits_compute=dp.get_precision('Account')), + 'writeoff': fields.float('Write-Off amount', readonly=True, digits_compute=dp.get_precision('Account')), } def default_get(self, cr, uid, fields, context=None): diff --git a/addons/account_analytic_analysis/account_analytic_analysis_menu.xml b/addons/account_analytic_analysis/account_analytic_analysis_menu.xml index ffbe48f0b19..d56b6ee4760 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis_menu.xml +++ b/addons/account_analytic_analysis/account_analytic_analysis_menu.xml @@ -48,7 +48,7 @@ <field name="name">Contracts to Renew</field> <field name="res_model">account.analytic.account</field> <field name="view_type">form</field> - <field name="view_mode">tree,form,graph</field> + <field name="view_mode">tree,form</field> <field name="context">{'search_default_user_id':uid, 'search_default_draft':1, 'search_default_pending':1, 'search_default_open':1, 'search_default_renew':1}</field> <field name="domain">[('type','=','contract')]</field> <field name="search_view_id" ref="view_account_analytic_account_overdue_search"/> @@ -72,7 +72,7 @@ <field name="name">Contracts</field> <field name="res_model">account.analytic.account</field> <field name="view_type">form</field> - <field name="view_mode">tree,form,graph</field> + <field name="view_mode">tree,form</field> <field name="context">{'default_type':'contract', 'search_default_open':1, 'search_default_pending':1}</field> <field name="domain">[('type','=','contract')]</field> <field name="search_view_id" ref="view_account_analytic_account_overdue_search"/> diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py index b6828062a85..e6861d0db67 100644 --- a/addons/account_asset/account_asset.py +++ b/addons/account_asset/account_asset.py @@ -350,7 +350,7 @@ class account_asset_depreciation_line(osv.osv): 'amount': fields.float('Depreciation Amount', required=True), 'remaining_value': fields.float('Amount to Depreciate', required=True), 'depreciated_value': fields.float('Amount Already Depreciated', required=True), - 'depreciation_date': fields.char('Depreciation Date', size=64, select=1), + 'depreciation_date': fields.date('Depreciation Date', select=1), 'move_id': fields.many2one('account.move', 'Depreciation Entry'), 'move_check': fields.function(_get_move_check, method=True, type='boolean', string='Posted', store=True) } @@ -368,7 +368,7 @@ class account_asset_depreciation_line(osv.osv): for line in self.browse(cr, uid, ids, context=context): if currency_obj.is_zero(cr, uid, line.asset_id.currency_id, line.remaining_value): can_close = True - depreciation_date = line.asset_id.prorata and line.asset_id.purchase_date or time.strftime('%Y-%m-%d') + depreciation_date = time.strftime('%Y-%m-%d') period_ids = period_obj.find(cr, uid, depreciation_date, context=context) company_currency = line.asset_id.company_id.currency_id.id current_currency = line.asset_id.currency_id.id diff --git a/addons/account_asset/account_asset_invoice.py b/addons/account_asset/account_asset_invoice.py index 700832881c4..4b38834bdc3 100644 --- a/addons/account_asset/account_asset_invoice.py +++ b/addons/account_asset/account_asset_invoice.py @@ -57,6 +57,7 @@ class account_invoice_line(osv.osv): 'partner_id': line.invoice_id.partner_id.id, 'company_id': line.invoice_id.company_id.id, 'currency_id': line.invoice_id.currency_id.id, + 'purchase_date' : line.invoice_id.date_invoice, } changed_vals = asset_obj.onchange_category_id(cr, uid, [], vals['category_id'], context=context) vals.update(changed_vals['value']) diff --git a/addons/account_asset/report/account_asset_report.py b/addons/account_asset/report/account_asset_report.py index 000967a555c..583e5534613 100644 --- a/addons/account_asset/report/account_asset_report.py +++ b/addons/account_asset/report/account_asset_report.py @@ -50,7 +50,7 @@ class asset_asset_report(osv.osv): select min(dl.id) as id, dl.name as name, - to_date(dl.depreciation_date, 'YYYY-MM-DD') as depreciation_date, + dl.depreciation_date as depreciation_date, a.purchase_date as purchase_date, (CASE WHEN (select min(d.id) from account_asset_depreciation_line as d left join account_asset_asset as ac ON (ac.id=d.asset_id) @@ -77,7 +77,7 @@ class asset_asset_report(osv.osv): from account_asset_depreciation_line dl left join account_asset_asset a on (dl.asset_id=a.id) group by - dl.amount,dl.asset_id,to_date(dl.depreciation_date, 'YYYY-MM-DD'),dl.name, + dl.amount,dl.asset_id,dl.depreciation_date,dl.name, a.purchase_date, dl.move_check, a.state, a.category_id, a.partner_id, a.company_id, a.purchase_value, a.id, a.salvage_value )""") diff --git a/addons/account_bank_statement_extensions/account_bank_statement_view.xml b/addons/account_bank_statement_extensions/account_bank_statement_view.xml index 30b2778817c..1c076426752 100644 --- a/addons/account_bank_statement_extensions/account_bank_statement_view.xml +++ b/addons/account_bank_statement_extensions/account_bank_statement_view.xml @@ -76,7 +76,7 @@ <field name="ref" readonly="1"/> <field name="partner_id" on_change="onchange_partner_id(partner_id)"/> <field name="type" on_change="onchange_type(partner_id, type)"/> - <field name="account_id" domain="[('journal_id','=',parent.journal_id)]"/> + <field name="account_id" domain="[('journal_id','=',journal_id)]"/> <field name="analytic_account_id" groups="analytic.group_analytic_accounting" domain="[('company_id', '=', parent.company_id), ('type', '<>', 'view')]"/> <field name="amount" readonly="1" sum="Total Amount"/> <field name="globalisation_id" string="Glob. Id"/> @@ -100,7 +100,7 @@ <field name="ref" readonly="0"/> <field name="partner_id" on_change="onchange_partner_id(partner_id)"/> <field name="type" on_change="onchange_type(partner_id, type)"/> - <field domain="[('journal_id', '=', parent.journal_id), ('type', '<>', 'view')]" name="account_id"/> + <field domain="[('journal_id', '=', journal_id), ('type', '<>', 'view')]" name="account_id"/> <field name="analytic_account_id" groups="analytic.group_analytic_accounting" domain="[('company_id', '=', parent.company_id), ('type', '<>', 'view')]"/> <field name="amount"/> <field name="globalisation_id"/> diff --git a/addons/account_payment/wizard/account_payment_order.py b/addons/account_payment/wizard/account_payment_order.py index 713784798fc..455475b0ae5 100644 --- a/addons/account_payment/wizard/account_payment_order.py +++ b/addons/account_payment/wizard/account_payment_order.py @@ -88,7 +88,7 @@ class payment_order_create(osv.osv_memory): 'partner_id': line.partner_id and line.partner_id.id or False, 'communication': line.ref or '/', 'date': date_to_pay, - 'currency': line.invoice and line.invoice.currency_id.id or False, + 'currency': (line.invoice and line.invoice.currency_id.id) or line.journal_id.currency.id or line.journal_id.company_id.currency_id.id, }, context=context) return {'type': 'ir.actions.act_window_close'} @@ -120,4 +120,4 @@ class payment_order_create(osv.osv_memory): payment_order_create() -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index 36aa8660bc4..30358f701ef 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -32,11 +32,11 @@ class res_company(osv.osv): _columns = { 'income_currency_exchange_account_id': fields.many2one( 'account.account', - string="Income Currency Rate", + string="Gain Exchange Rate Account", domain="[('type', '=', 'other')]",), 'expense_currency_exchange_account_id': fields.many2one( 'account.account', - string="Expense Currency Rate", + string="Loss Exchange Rate Account", domain="[('type', '=', 'other')]",), } @@ -957,11 +957,11 @@ class account_voucher(osv.osv): if amount_residual > 0: account_id = line.voucher_id.company_id.expense_currency_exchange_account_id if not account_id: - raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Income Currency Rate' on the company, then create accounting entry for currency rate difference.")) + raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Expense Currency Rate' on the company, then create accounting entry for currency rate difference.")) else: account_id = line.voucher_id.company_id.income_currency_exchange_account_id if not account_id: - raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Expense Currency Rate' on the company, then create accounting entry for currency rate difference.")) + raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Income Currency Rate' on the company, then create accounting entry for currency rate difference.")) # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise # the receivable/payable account may have a secondary currency, which render this field mandatory account_currency_id = company_currency <> current_currency and current_currency or False diff --git a/addons/account_voucher/account_voucher_view.xml b/addons/account_voucher/account_voucher_view.xml index 5e26c366778..697d2affcf0 100644 --- a/addons/account_voucher/account_voucher_view.xml +++ b/addons/account_voucher/account_voucher_view.xml @@ -108,6 +108,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/account_voucher/voucher_payment_receipt_view.xml b/addons/account_voucher/voucher_payment_receipt_view.xml index 78b76e5c9b7..8e86e44942a 100644 --- a/addons/account_voucher/voucher_payment_receipt_view.xml +++ b/addons/account_voucher/voucher_payment_receipt_view.xml @@ -243,6 +243,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -518,6 +519,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/account_voucher/voucher_sales_purchase_view.xml b/addons/account_voucher/voucher_sales_purchase_view.xml index 5bcecfa268e..d3449cf6027 100644 --- a/addons/account_voucher/voucher_sales_purchase_view.xml +++ b/addons/account_voucher/voucher_sales_purchase_view.xml @@ -146,6 +146,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -301,6 +302,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/analytic/analytic_view.xml b/addons/analytic/analytic_view.xml index 51f5428002f..bed588015ad 100644 --- a/addons/analytic/analytic_view.xml +++ b/addons/analytic/analytic_view.xml @@ -53,6 +53,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/audittrail/audittrail.py b/addons/audittrail/audittrail.py index 3dff7c1cb00..332808d8a18 100644 --- a/addons/audittrail/audittrail.py +++ b/addons/audittrail/audittrail.py @@ -442,11 +442,13 @@ class audittrail_objects_proxy(object_proxy): # if at least one modification has been found for model_id, resource_id in lines: + name = pool.get(model.model).name_get(cr, uid, [resource_id])[0][1] vals = { 'method': method, 'object_id': model_id, 'user_id': uid, 'res_id': resource_id, + 'name': name, } if (model_id, resource_id) not in old_values and method not in ('copy', 'read'): # the resource was not existing so we are forcing the method to 'create' @@ -484,7 +486,7 @@ class audittrail_objects_proxy(object_proxy): if len(rule['user_id']) == 0 or uid in rule['user_id']: if rule.get('log_'+method,0): return True - elif method not in ('default_get','read','fields_view_get','fields_get','search','search_count','name_search','name_get','get','request_get', 'get_sc', 'unlink', 'write', 'create'): + elif method not in ('default_get','read','fields_view_get','fields_get','search','search_count','name_search','name_get','get','request_get', 'get_sc', 'unlink', 'write', 'create', 'read_group', 'import_data'): if rule['log_action']: return True diff --git a/addons/auth_ldap/users_ldap.py b/addons/auth_ldap/users_ldap.py index 47fc41c3645..c2c0ac557bf 100644 --- a/addons/auth_ldap/users_ldap.py +++ b/addons/auth_ldap/users_ldap.py @@ -180,8 +180,8 @@ class CompanyLDAP(osv.osv): """ user_id = False - login = tools.ustr(login) - cr.execute("SELECT id, active FROM res_users WHERE login=%s", (login,)) + login = tools.ustr(login.lower()) + cr.execute("SELECT id, active FROM res_users WHERE lower(login)=%s", (login,)) res = cr.fetchone() if res: if res[1]: diff --git a/addons/auth_ldap/users_ldap_view.xml b/addons/auth_ldap/users_ldap_view.xml index c4fc7cebbbe..47c15badd7c 100644 --- a/addons/auth_ldap/users_ldap_view.xml +++ b/addons/auth_ldap/users_ldap_view.xml @@ -14,7 +14,7 @@ <field name="ldap_server"/> <field name="ldap_server_port"/> <field name="ldap_binddn"/> - <field name="ldap_password"/> + <field name="ldap_password" password="True"/> <field name="ldap_base"/> <field name="ldap_filter"/> <field name="create_user"/> diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index 7555621945d..5b8c41787d4 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -971,8 +971,8 @@ class calendar_event(osv.osv): event = datas['id'] if datas.get('interval', 0) < 0: raise osv.except_osv(_('Warning!'), _('Interval cannot be negative.')) - if datas.get('count', 0) < 0: - raise osv.except_osv(_('Warning!'), _('Count cannot be negative.')) + if datas.get('count', 0) <= 0: + raise osv.except_osv(_('Warning!'), _('Count cannot be negative or 0.')) if datas['recurrency']: result[event] = self.compute_rule_string(datas) else: @@ -1113,6 +1113,16 @@ rule or repeating pattern of time to exclude from the recurring rule."), 'user_id': lambda self, cr, uid, ctx: uid, 'organizer': default_organizer, } + + def _check_closing_date(self, cr, uid, ids, context=None): + for event in self.browse(cr, uid, ids, context=context): + if event.date_deadline < event.date: + return False + return True + + _constraints = [ + (_check_closing_date, 'Error ! End date cannot be set before start date.', ['date_deadline']), + ] def get_recurrent_ids(self, cr, uid, select, domain, limit=100, context=None): """Gives virtual event ids for recurring events based on value of Recurrence Rule diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml index 2da18447820..8373d32ab0d 100644 --- a/addons/base_calendar/crm_meeting_view.xml +++ b/addons/base_calendar/crm_meeting_view.xml @@ -182,13 +182,14 @@ <button name="do_tentative" states="needs-action,declined,accepted" string="Uncertain" type="object" + icon="terp-crm" /> <button name="do_accept" string="Accept" states="needs-action,tentative,declined" - type="object" /> + type="object" icon="gtk-apply"/> <button name="do_decline" string="Decline" states="needs-action,tentative,accepted" - type="object" /> + type="object" icon="gtk-cancel"/> </tree> <form string="Invitation details" version="7.0"> <header> @@ -222,6 +223,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 2a7996655a2..dd36aa19041 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -36,7 +36,7 @@ It manages key tasks such as communication, identification, prioritization, assi OpenERP ensures that all cases are successfully tracked by users, customers and suppliers. It can automatically send reminders, escalate the request, trigger specific methods and many other actions based on your own enterprise rules. -The greatest thing about this system is that users don't need to do anything special. The CRM module has an email gateway for the synchronization interface between mails and OpenERP. That way, users can just send emails to the request tracker. +The greatest thing about this system is that users don't need to do anything special. The CRM module has an email gateway for the synchronization interface between mails and OpenERP. That way, users can just send emails to the request tracker. OpenERP will take care of thanking them for their message, automatically routing it to the appropriate staff and make sure all future correspondence gets to the right place. @@ -99,7 +99,7 @@ Dashboard for CRM will include: 'res_partner_view.xml', 'board_crm_view.xml', - + 'res_config_view.xml', ], @@ -115,9 +115,10 @@ Dashboard for CRM will include: 'test/process/cancel_lead.yml', 'test/process/action_rule.yml', 'test/process/segmentation.yml', + 'test/process/phonecalls.yml', 'test/ui/crm_demo.yml', 'test/ui/duplicate_lead.yml', - 'test/ui/delete_lead.yml' + 'test/ui/delete_lead.yml', ], 'installable': True, 'application': True, diff --git a/addons/crm/crm_demo.xml b/addons/crm/crm_demo.xml index 551e4e93fb8..dde3e63cc4a 100644 --- a/addons/crm/crm_demo.xml +++ b/addons/crm/crm_demo.xml @@ -1,46 +1,47 @@ <?xml version="1.0" encoding="utf-8"?> <openerp> - <data> + <data noupdate="1"> + <record id="base.user_demo" model="res.users"> - <field name="groups_id" eval="[(4,ref('base.group_sale_salesman'))]"/> + <field name="groups_id" eval="[(4,ref('base.group_sale_salesman'))]"/> </record> - <record id="ir_ui_view_sc_calendar_demo" model="ir.ui.view_sc"> - <field name="name">Meetings</field> - <field name="resource">ir.ui.menu</field> - <field name="user_id" ref="base.user_demo"/> - <field name="res_id" ref="base_calendar.menu_crm_meeting"/> - </record> + <record id="ir_ui_view_sc_calendar_demo" model="ir.ui.view_sc"> + <field name="name">Meetings</field> + <field name="resource">ir.ui.menu</field> + <field name="user_id" ref="base.user_demo"/> + <field name="res_id" ref="base_calendar.menu_crm_meeting"/> + </record> - <record model="crm.case.section" id="crm_case_section_1"> + <record model="crm.case.section" id="crm_case_section_1"> <field name="name">Sales Marketing Department</field> <field name="code">SMD</field> - <field name="parent_id" ref="crm.section_sales_department"></field> - </record> + <field name="parent_id" ref="crm.section_sales_department"/> + </record> - <record model="crm.case.section" id="crm_case_section_2"> + <record model="crm.case.section" id="crm_case_section_2"> <field name="name">Support Department</field> <field name="code">SPD</field> - <field name="parent_id" ref="crm.section_sales_department"></field> - </record> + <field name="parent_id" ref="crm.section_sales_department"/> + </record> - <record model="crm.case.section" id="crm_case_section_3"> + <record model="crm.case.section" id="crm_case_section_3"> <field name="name">Direct Marketing</field> <field name="code">DM</field> - <field name="parent_id" ref="crm.section_sales_department"></field> - </record> + <field name="parent_id" ref="crm.section_sales_department"/> + </record> - <record model="crm.case.section" id="crm_case_section_4"> + <record model="crm.case.section" id="crm_case_section_4"> <field name="name">Online Support</field> <field name="code">OS</field> - <field name="parent_id" ref="crm.crm_case_section_2"></field> - </record> - - <record model="crm.segmentation" id="crm_segmentation0"> - <field name="name">OpenERP partners</field> - <field name="exclusif">True</field> - <field name="categ_id" ref="base.res_partner_category_2"/> - </record> + <field name="parent_id" ref="crm.crm_case_section_2"/> + </record> + + <record model="crm.segmentation" id="crm_segmentation0"> + <field name="name">OpenERP partners</field> + <field name="exclusif">True</field> + <field name="categ_id" ref="base.res_partner_category_2"/> + </record> </data> </openerp> diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index d3f7e1d77ef..d668ea710c1 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -298,7 +298,7 @@ class crm_lead(base_stage, format_address, osv.osv): if partner_id: partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) values = { - 'partner_name' : partner.name, + 'partner_name' : partner.name, 'street' : partner.street, 'street2' : partner.street2, 'city' : partner.city, @@ -495,7 +495,8 @@ class crm_lead(base_stage, format_address, osv.osv): title = "%s : %s" % (merge_message, opportunity.name) details.append(self._mail_body(cr, uid, opportunity, fields, title=title, context=context)) - subject = subject[0] + ", ".join(subject[1:]) + # Chatter message's subject + subject = subject[0] + ": " + ", ".join(subject[1:]) details = "\n\n".join(details) return self.message_post(cr, uid, [opportunity_id], body=details, subject=subject, context=context) @@ -564,19 +565,18 @@ class crm_lead(base_stage, format_address, osv.osv): data = self._merge_data(cr, uid, ids, oldest, fields, context=context) - # merge data into first opportunity - self.write(cr, uid, [first_opportunity.id], data, context=context) - - #copy message and attachements into the first opportunity + # Merge messages and attachements into the first opportunity self._merge_opportunity_history(cr, uid, first_opportunity.id, tail_opportunities, context=context) self._merge_opportunity_attachments(cr, uid, first_opportunity.id, tail_opportunities, context=context) - #Notification about loss of information + # Merge notifications about loss of information self._merge_notification(cr, uid, first_opportunity, opportunities, context=context) - #delete tail opportunities + # Write merged data into first opportunity + self.write(cr, uid, [first_opportunity.id], data, context=context) + # Delete tail opportunities self.unlink(cr, uid, [x.id for x in tail_opportunities], context=context) - #open first opportunity + # Open first opportunity self.case_open(cr, uid, [first_opportunity.id]) return first_opportunity.id @@ -585,13 +585,16 @@ class crm_lead(base_stage, format_address, osv.osv): contact_id = False if customer: contact_id = self.pool.get('res.partner').address_get(cr, uid, [customer.id])['default'] + if not section_id: section_id = lead.section_id and lead.section_id.id or False + if section_id: stage_ids = crm_stage.search(cr, uid, [('sequence','>=',1), ('section_ids','=', section_id)]) else: stage_ids = crm_stage.search(cr, uid, [('sequence','>=',1)]) stage_id = stage_ids and stage_ids[0] or False + return { 'planned_revenue': lead.planned_revenue, 'probability': lead.probability, @@ -608,7 +611,6 @@ class crm_lead(base_stage, format_address, osv.osv): def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, section_id=False, context=None): partner = self.pool.get('res.partner') - mail_message = self.pool.get('mail.message') customer = False if partner_id: customer = partner.browse(cr, uid, partner_id, context=context) diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml index b3c00256b06..7849125282e 100644 --- a/addons/crm/crm_lead_demo.xml +++ b/addons/crm/crm_lead_demo.xml @@ -1,10 +1,8 @@ <?xml version="1.0"?> <openerp> <data noupdate="1"> - <!-- - Demo Leads - --> + <!-- Demo Leads --> <record id="crm_case_1" model="crm.lead"> <field name="type">lead</field> <field name="name">Plan to Attend a Training</field> @@ -26,9 +24,9 @@ <field name="user_id" ref="base.user_root"/> <field name="stage_id" ref="stage_lead1"/> <field name="description">Hello, -I am Jason from Le Club SARL, -I am intertested to attend Training organized in your company, -Can you send details,</field> +I am Jason from Le Club SARL. +I am intertested to attend a training organized in your company. +Can you send me the details ?</field> <field eval="1" name="active"/> </record> @@ -110,7 +108,8 @@ Can you send details,</field> <field name="section_id" ref="section_sales_department"/> <field name="user_id" ref="base.user_root"/> <field name="stage_id" ref="stage_lead1"/> - <field name="description">Hi, Can you send a quotation for 20 Computers with speakers? + <field name="description">Hi, +Can you send me a quotation for 20 computers with speakers? Regards, Carrie Helle, Purchase Manager @@ -215,8 +214,9 @@ Contact: +1 813 494 5005</field> <field name="section_id" ref="crm_case_section_3"/> <field name="user_id" ref=""/> <field name="stage_id" ref="stage_lead1"/> - <field name="description">hi, -I want to know specification and cost of laptops of your company. + <field name="description">Hi, +I would like to know more about specification and cost of laptops of your company. + Thanks, Andrew</field> <field eval="1" name="active"/> @@ -260,25 +260,20 @@ Andrew</field> <field eval="1" name="active"/> </record> - - <!-- Call Function to Cancel the leads (set as Dead) --> + <!-- Call Function to Cancel the leads (set as Dead) --> <function model="crm.lead" name="case_cancel" eval="[ ref('crm_case_12'), ref('crm_case_7'), ref('crm_case_3'), ref('crm_case_8')], {'install_mode': True}" /> - <!-- Call Function to set the leads as Unread --> + + <!-- Call Function to set the leads as Unread --> <function model="crm.lead" name="message_mark_as_unread" eval="[ ref('crm_case_1'), ref('crm_case_2'), ref('crm_case_5'), ref('crm_case_11')], {}" /> - - <!-- - Demo Opportunities - --> - - + <!-- Demo Opportunities --> <record id="crm_case_13" model="crm.lead"> <field name="type">opportunity</field> <field name="name">Plan to buy 60 keyboards and mouses</field> @@ -563,9 +558,9 @@ Andrew</field> <field name="body">Dear Customer, Thanks for showing interest in our products. We have attached the catalogue, -We would like to know your interests, Let us know if we can call you for more details. +We would like to know your interests, so let us know when we can call you for more details. -Thanks</field> +Regards</field> <field name="parent_id" ref="message_email0"/> <field name="author_id" ref="base.partner_root"/> </record> @@ -600,9 +595,9 @@ Thanks</field> <field name="model">crm.lead</field> <field name="res_id" ref="crm_case_1"/> <field name="body">Hello, -I am Jason from Le Club SARL, -I am intertested to attend Training organized in your company, -Can you send details,</field> +I am Jason from Le Club SARL. +I am intertested to attend a training organized in your company. +Can you send me the details ?</field> <field name="type">email</field> </record> <record id="message_email_13" model="mail.message"> @@ -613,7 +608,7 @@ Can you send details,</field> <field name="type">comment</field> </record> - <!-- Call Function to set the opportunities as Unread --> + <!-- Call Function to set the opportunities as Unread --> <function model="crm.lead" name="message_mark_as_unread" eval="[ ref('crm_case_15'), ref('crm_case_16'), ref('crm_case_23'), ref('crm_case_19')], {}" diff --git a/addons/crm/crm_lead_menu.xml b/addons/crm/crm_lead_menu.xml index 04758f4ffe0..208d92a6684 100644 --- a/addons/crm/crm_lead_menu.xml +++ b/addons/crm/crm_lead_menu.xml @@ -14,12 +14,13 @@ <p class="oe_view_nocontent_create"> Click to create an unqualified lead. </p><p> - A lead is usually the first step in your sales cycle. It can be - a contact with a new prospect, a company you target, a contact form - filled in your website, etc. + Use leads if you need a qualification step before creating an + opportunity or a customer. It can be a business card you received, + a contact form filled in your website, or a file of unqualified + prospects you import, etc. </p><p> Once qualified, the lead can be converted into a business - opportunity and a new customer in your address book. + opportunity and/or a new customer in your address book. </p> </field> </record> diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index f91a8756598..f0311f8fce5 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -223,6 +223,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -525,6 +526,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/crm/crm_phonecall_demo.xml b/addons/crm/crm_phonecall_demo.xml index cbfb74a8e04..f441c29a7e6 100644 --- a/addons/crm/crm_phonecall_demo.xml +++ b/addons/crm/crm_phonecall_demo.xml @@ -1,9 +1,8 @@ <?xml version="1.0"?> <openerp> <data noupdate="1"> - <!-- - ((((((((((( Demo Cases ))))))))))) - --> + + <!-- Demo Cases --> <record id="crm_phonecall_1" model="crm.phonecall"> <field eval="time.strftime('%Y-%m-04 10:45:36')" name="date"/> <field name="partner_id" ref="base.res_partner_11"/> @@ -47,7 +46,7 @@ <field eval="time.strftime('%Y-%m-21 14:10:23')" name="date"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field name="name">Wanted information about pricing of Laptops</field> + <field name="name">Wanted information about pricing of laptops</field> <field name="state">done</field> <field name="partner_phone">(077) 582-4035</field> <field name="partner_mobile">(077) 341-3591</field> @@ -79,6 +78,7 @@ <field eval="time.strftime('%Y-%m-28 14:15:30')" name="date"/> <field name="categ_id" ref="crm.categ_phone2"/> <field eval="8.56" name="duration"/> - </record> + </record> + </data> </openerp> diff --git a/addons/crm/crm_phonecall_menu.xml b/addons/crm/crm_phonecall_menu.xml index ba8c4768c09..c359aa461e7 100644 --- a/addons/crm/crm_phonecall_menu.xml +++ b/addons/crm/crm_phonecall_menu.xml @@ -6,8 +6,8 @@ id="crm_case_categ_phone_create_partner" name="Schedule a Call" res_model="crm.phonecall" - view_mode="calendar,tree,form" - context="{'search_default_partner_id': [active_id], 'default_duration': 1.0, 'default_partner_id': active_id}" + view_mode="tree,form,calendar" + context="{'search_default_partner_id': active_id, 'default_duration': 1.0, 'default_partner_id': active_id}" groups="base.group_sale_salesman" /> diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml index 8c1e1c9ec76..b8bc6b3a13e 100644 --- a/addons/crm/crm_phonecall_view.xml +++ b/addons/crm/crm_phonecall_view.xml @@ -146,10 +146,10 @@ <field name="priority"/> <field name="opportunity_id"/> </group> - <field name="description" placeholder="Description..."/> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/crm/res_partner.py b/addons/crm/res_partner.py index 55cc0c95b2c..e43534476fd 100644 --- a/addons/crm/res_partner.py +++ b/addons/crm/res_partner.py @@ -26,12 +26,16 @@ class res_partner(osv.osv): _inherit = 'res.partner' def _opportunity_meeting_count(self, cr, uid, ids, field_name, arg, context=None): - res = {} - for partner in self.browse(cr, uid, ids, context): - res[partner.id] = { - 'opportunity_count': len(partner.opportunity_ids), - 'meeting_count': len(partner.meeting_ids), - } + res = dict(map(lambda x: (x,{'opportunity_count': 0, 'meeting_count': 0}), ids)) + # the user may not have access rights for opportunities or meetings + try: + for partner in self.browse(cr, uid, ids, context): + res[partner.id] = { + 'opportunity_count': len(partner.opportunity_ids), + 'meeting_count': len(partner.meeting_ids), + } + except: + pass return res _columns = { diff --git a/addons/crm/res_partner_view.xml b/addons/crm/res_partner_view.xml index eae78de70a0..84a0bd4cf49 100644 --- a/addons/crm/res_partner_view.xml +++ b/addons/crm/res_partner_view.xml @@ -54,7 +54,7 @@ <field name="src_model">res.partner</field> <field name="res_model">crm.meeting</field> <field name="view_mode">calendar,tree,form,gantt</field> - <field name="context">{'search_default_partner_ids': active_id}</field> + <field name="context">{'default_partner_ids': [active_id]}</field> </record> <!-- open opportunities related to given partner --> diff --git a/addons/crm/test/process/action_rule.yml b/addons/crm/test/process/action_rule.yml index 9c869009906..f76e6c250bc 100644 --- a/addons/crm/test/process/action_rule.yml +++ b/addons/crm/test/process/action_rule.yml @@ -7,14 +7,14 @@ new_id = self.create(cr, uid, {'name': 'New Rule', 'model_id': model_ids[0], 'trg_user_id': ref('base.user_root'), 'trg_partner_id': ref('base.res_partner_1'), 'act_user_id': ref('base.user_demo') }) self._check(cr, uid) - - I create new lead to check record rule. + I create a new lead to check the record rule. - !record {model: crm.lead, id: crm_lead_test_rules_id}: name: 'Test lead rules' partner_id: base.res_partner_1 - - I check record rule is apply and responsible is changed. + I check if the record rule is applied and the responsible is changed. - !python {model: crm.lead}: | lead_user = self.browse(cr, uid, ref('crm_lead_test_rules_id')) - assert lead_user.user_id.id == ref('base.user_demo'), "Responsible of lead is not changed." + assert lead_user.user_id.id == ref('base.user_demo'), "Responsible of lead is not changed." diff --git a/addons/crm/test/process/cancel_lead.yml b/addons/crm/test/process/cancel_lead.yml index 31c159c3fdf..edaf844952a 100644 --- a/addons/crm/test/process/cancel_lead.yml +++ b/addons/crm/test/process/cancel_lead.yml @@ -10,7 +10,7 @@ lead = self.browse(cr, uid, ref('crm_case_1')) assert lead.stage_id.id == ref('crm.stage_lead7'), "Stage should be 'Dead' and is %s." % (lead.stage_id.name) assert lead.state == 'cancel', "Opportunity is not in 'cancel' state." - assert lead.probability == 0.0, 'Opportunity probability is wrong and should be 0.0.' + assert lead.probability == 0.0, 'Opportunity is probably wrong and should be 0.0.' - I reset cancelled lead into unqualified lead. - diff --git a/addons/crm/test/process/communication_with_customer.yml b/addons/crm/test/process/communication_with_customer.yml index ec11528d15a..2137616ae3e 100644 --- a/addons/crm/test/process/communication_with_customer.yml +++ b/addons/crm/test/process/communication_with_customer.yml @@ -1,7 +1,7 @@ - - Customer interested in our product. so he send request by email to get more details. + Customer interested in our product, so he sends request by email to get more details. - - Mail script will be fetched him request from mail server. so I process that mail after read EML file + Mail script will fetch his request from mail server. Then I process that mail after read EML file. - !python {model: mail.thread}: | import addons @@ -15,27 +15,27 @@ lead_ids = self.search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')]) assert lead_ids and len(lead_ids), "Lead is not created after getting request" lead = self.browse(cr, uid, lead_ids[0], context=context) - assert not lead.partner_id, "Customer should be a new" + assert not lead.partner_id, "Customer should be a new one" assert lead.name == "Fournir votre devis avec le meilleur prix.", "Subject does not match" - - I reply him request with welcome message. TODO revert mail.mail to mail.compose.message (conversion to customer should be automatic). + I reply his request with welcome message. TODO revert mail.mail to mail.compose.message (conversion to customer should be automatic). - !python {model: mail.mail}: | lead_ids = self.pool.get('crm.lead').search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')]) context.update({'active_model': 'crm.lead','active_id': lead_ids[0]}) - id = self.create(cr, uid, {'body': "Merci à l'intérêt pour notre produit.nous vous contacterons bientôt. Merci", 'email_from': 'sales@mycompany.com'}, context=context) + id = self.create(cr, uid, {'body': "Merci de votre intérêt pour notre produit, nous vous contacterons bientôt. Bien à vous", 'email_from': 'sales@mycompany.com'}, context=context) try: self.send_mail(cr, uid, [id], context=context) except: pass - - Now, I convert him into customer and put into regular customer list. + Now, I convert him into customer and put him into regular customer list. - !python {model: crm.lead}: | lead_ids = self.search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')]) self.convert_partner(cr, uid, lead_ids, context=context) - - I convert one phonecall request as a customer and put into regular customer list. + I convert one phonecall request to a customer and put him into regular customer list. - !python {model: crm.phonecall2partner}: | context.update({'active_model': 'crm.phonecall', 'active_ids': [ref("crm.crm_phonecall_4")], 'active_id': ref("crm.crm_phonecall_4")}) @@ -48,5 +48,5 @@ partner_id = self.search(cr, uid, [('phonecall_ids', '=', ref('crm.crm_phonecall_4'))]) assert partner_id, "Customer is not found in regular customer list." data = self.browse(cr, uid, partner_id, context=context)[0] - assert data.user_id.id == ref("base.user_root"), "User not assign properly" - assert data.name == "Wanted information about pricing of Laptops", "Bad partner name" + assert data.user_id.id == ref("base.user_root"), "User not assigned properly" + assert data.name == "Wanted information about pricing of laptops", "Bad partner name" diff --git a/addons/crm/test/process/lead2opportunity2win.yml b/addons/crm/test/process/lead2opportunity2win.yml index ed552421cf0..eadb46f0f55 100644 --- a/addons/crm/test/process/lead2opportunity2win.yml +++ b/addons/crm/test/process/lead2opportunity2win.yml @@ -1,17 +1,17 @@ - - In order to test convert customer lead into opportunity, + In order to test convert customer lead into opportunity, - I open customer lead. - !python {model: crm.lead}: | self.case_open(cr, uid, [ref("crm_case_4")]) -- - I check lead state is "Open". +- + I check lead state is "Open". - !assert {model: crm.lead, id: crm.crm_case_4, string: Lead in open state}: - state == "open" - - I create partner from lead. + I create partner from lead. - !record {model: crm.lead2partner, id: crm_lead2partner_id1, context: '{"active_model": "crm.lead", "active_ids": [ref("crm_case_4")]}'}: - @@ -24,7 +24,7 @@ !python {model: crm.lead}: | self.convert_opportunity(cr, uid ,[ref("crm_case_4")], ref("base.res_partner_2")) - - I check details of converted opportunity. + I check details of converted opportunity. - !python {model: crm.lead}: | lead = self.browse(cr, uid, ref('crm_case_4')) @@ -37,15 +37,15 @@ !python {model: crm.opportunity2phonecall}: | import time context.update({'active_model': 'crm.lead', 'active_ids': [ref('crm_case_4')]}) - call_id = self.create(cr, uid, {'date': time.strftime('%Y-%m-%d %H:%M:%S'), - 'name': "Bonjour M. Jean, Comment êtes-vous? J'ai obtenu votre demande. peut-on parler au sujet de ce pour quelques minutes?"}, context=context) + call_id = self.create(cr, uid, {'date': time.strftime('%Y-%m-%d %H:%M:%S'), + 'name': "Bonjour M. Jean, Comment allez-vous? J'ai bien reçu votre demande, pourrions-nous en parler quelques minutes?"}, context=context) self.action_schedule(cr, uid, [call_id], context=context) - I check that phonecall is scheduled for that opportunity. - !python {model: crm.phonecall}: | ids = self.search(cr, uid, [('opportunity_id', '=', ref('crm_case_4'))]) - assert len(ids), 'phonecall is not scheduled' + assert len(ids), 'Phonecall is not scheduled' - Now I schedule meeting with customer. - @@ -55,7 +55,7 @@ After communicated with customer, I put some notes with contract details. - !python {model: crm.lead}: | - self.message_post(cr, uid, [ref('crm_case_4')], subject='Test note', body='ces détails envoyés par le client sur ​​le FAX pour la qualité') + self.message_post(cr, uid, [ref('crm_case_4')], subject='Test note', body='Détails envoyés par le client sur ​​le FAX pour la qualité') - I win this opportunity - @@ -73,7 +73,7 @@ I convert mass lead into opportunity customer. - !python {model: crm.lead2opportunity.partner.mass}: | - context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_11"), ref("crm_case_2")], 'active_id': ref("crm_case_11")}) + context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_11"), ref("crm_case_2")], 'active_id': ref("crm_case_11")}) id = self.create(cr, uid, {'user_ids': [ref('base.user_root')], 'section_id': ref('crm.section_sales_department')}, context=context) self.mass_convert(cr, uid, [id], context=context) - @@ -111,7 +111,7 @@ I confirm review needs meeting. - !python {model: crm.meeting}: | - context.update({'active_model': 'crm.meeting'}) + context.update({'active_model': 'crm.meeting'}) self.case_open(cr, uid, [ref('base_calendar.crm_meeting_4')]) - I invite a user for meeting. diff --git a/addons/crm/test/process/merge_opportunity.yml b/addons/crm/test/process/merge_opportunity.yml index 6d13278eded..155cd68274f 100644 --- a/addons/crm/test/process/merge_opportunity.yml +++ b/addons/crm/test/process/merge_opportunity.yml @@ -1,19 +1,19 @@ - - I make direct opportunity for customer. + I make an opportunity from customer. - !python {model: crm.partner2opportunity}: | context.update({'active_model': 'res.partner', 'active_ids': [ref("base.res_partner_9")]}) - res_id = self.create(cr, uid, {'name': "enquête pour l'achat de services"}, context=context) + res_id = self.create(cr, uid, {'name': "Enquête pour l'achat de services"}, context=context) self.make_opportunity(cr, uid, [res_id], context=context) - - I make another opportunity from phonecall for same customer. + I make another opportunity from a phone call with the same customer. - !python {model: crm.phonecall2opportunity}: | context.update({'active_model': 'crm.phonecall', 'active_ids': [ref("crm.crm_phonecall_6")]}) - res_id = self.create(cr, uid, {'name': "Quoi de prix de votre autre service?", 'partner_id': ref("base.res_partner_9")}, context=context) + res_id = self.create(cr, uid, {'name': "À propos du prix de votre autre service", 'partner_id': ref("base.res_partner_9")}, context=context) self.make_opportunity(cr, uid, [res_id], context=context) - - Now I merge all opportunities of customer. + Now I merge these opportunities. - !python {model: crm.lead}: | opportunity_ids = self.search(cr, uid, [('partner_id','=', ref("base.res_partner_9"))]) @@ -24,7 +24,7 @@ !python {model: crm.merge.opportunity}: | self.action_merge(cr, uid, [ref("opportunity_merge_id")], context=context) - - I check for merged opportunities for customer. + I check for merged opportunities for customer. - !python {model: crm.lead}: | merge_id = self.search(cr, uid, [('partner_id','=', ref("base.res_partner_9"))]) @@ -32,55 +32,3 @@ merge_data = self.browse(cr, uid, merge_id)[0] assert merge_data.type == 'opportunity', 'Merged opportunity type not change!' assert merge_data.partner_id.id == ref("base.res_partner_9"), 'Partner missmatch!' -- - Now I schedule another phonecall to customer after merged. -- - !python {model: crm.phonecall2phonecall}: | - context.update({'active_model': 'crm.phonecall', 'active_ids': [ref("crm.crm_phonecall_6")], 'active_id': ref("crm.crm_phonecall_6")}) - res_id = self.create(cr, uid, {'name': "vos chances sont fusionnés en un seul"}, context=context) - self.action_schedule(cr, uid, [res_id], context=context) -- - I schedule meeting on this phonecall. -- - !python {model: crm.phonecall}: | - self.action_make_meeting(cr, uid, [ref("crm.crm_phonecall_6")]) -- - I set phone call to not held. -- - !python {model: crm.phonecall}: | - self.case_pending(cr, uid, [ref("crm.crm_phonecall_6")]) -- - I check that the phone call is in 'Not Held' state. -- - !assert {model: crm.phonecall, id: crm.crm_phonecall_6, string: Phone call held.}: - - state == "pending" -- - I cancelled the phone call. -- - !python {model: crm.phonecall}: | - self.case_cancel(cr, uid, [ref("crm.crm_phonecall_6")]) -- - I check that the phone call is in 'Cancelled' state. -- - !assert {model: crm.phonecall, id: crm.crm_phonecall_6, string: Phone call is not cancelled.}: - - state == "cancel" -- - I reset the phone call. -- - !python {model: crm.phonecall}: | - self.case_reset(cr, uid, [ref("crm.crm_phonecall_6")]) -- - I check that the phone call is reset or not. -- - !assert {model: crm.phonecall, id: crm.crm_phonecall_6, string: Phone call is not reset.}: - - state == "open" -- - I set phone call to held (done). -- - !python {model: crm.phonecall}: | - self.case_close(cr, uid, [ref("crm.crm_phonecall_6")]) -- - I check that the phone call is in 'Held' state. -- - !assert {model: crm.phonecall, id: crm.crm_phonecall_6, string: Phone call is not held.}: - - state == "done" diff --git a/addons/crm/test/process/phonecalls.yml b/addons/crm/test/process/phonecalls.yml new file mode 100644 index 00000000000..51a933f3223 --- /dev/null +++ b/addons/crm/test/process/phonecalls.yml @@ -0,0 +1,52 @@ +- + I schedule a phone call with a customer. +- + !python {model: crm.phonecall2phonecall}: | + context.update({'active_model': 'crm.phonecall', 'active_ids': [ref("crm.crm_phonecall_6")], 'active_id': ref("crm.crm_phonecall_6")}) + res_id = self.create(cr, uid, {'name': "Proposition de réduction"}, context=context) + self.action_schedule(cr, uid, [res_id], context=context) +- + I schedule a meeting based on this phone call. +- + !python {model: crm.phonecall}: | + self.action_make_meeting(cr, uid, [ref("crm.crm_phonecall_6")]) +- + I set the phone call to not held. +- + !python {model: crm.phonecall}: | + self.case_pending(cr, uid, [ref("crm.crm_phonecall_6")]) +- + I check that the phone call is in 'Not Held' state. +- + !assert {model: crm.phonecall, id: crm.crm_phonecall_6, string: Phone call held.}: + - state == "pending" +- + I cancel the phone call. +- + !python {model: crm.phonecall}: | + self.case_cancel(cr, uid, [ref("crm.crm_phonecall_6")]) +- + I check that the phone call is in 'Cancelled' state. +- + !assert {model: crm.phonecall, id: crm.crm_phonecall_6, string: Phone call is not cancelled.}: + - state == "cancel" +- + I reset the phone call. +- + !python {model: crm.phonecall}: | + self.case_reset(cr, uid, [ref("crm.crm_phonecall_6")]) +- + I check that the phone call is reset. +- + !assert {model: crm.phonecall, id: crm.crm_phonecall_6, string: Phone call is not reset.}: + - state == "open" +- + I set phone call to held (done). +- + !python {model: crm.phonecall}: | + self.case_close(cr, uid, [ref("crm.crm_phonecall_6")]) +- + I check that the phone call is in 'Held' state. +- + !assert {model: crm.phonecall, id: crm.crm_phonecall_6, string: Phone call is not held.}: + - state == "done" diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index 44ce73c23fe..2473a83206c 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -34,15 +34,17 @@ class crm_lead2opportunity_partner(osv.osv_memory): ('create', 'Create a new customer'), \ ('nothing', 'Do not link to a customer')], \ 'Related Customer', required=True), - 'name': fields.selection([('convert', 'Convert to Opportunities'), ('merge', 'Merge with existing Opportunities')], 'Conversion Action', required=True), + 'name': fields.selection([('convert', 'Convert to opportunity'), \ + ('merge', 'Merge with existing opportunities')], \ + 'Conversion Action', required=True), 'opportunity_ids': fields.many2many('crm.lead', string='Opportunities', domain=[('type', '=', 'opportunity')]), } def default_get(self, cr, uid, fields, context=None): """ - Default get for name, opportunity_ids - if there is an exisitng partner link to the lead, find all existing opportunity link with this partnet to merge - all information together + Default get for name, opportunity_ids. + If there is an exisitng partner link to the lead, find all existing + opportunities links with this partner to merge all information together """ lead_obj = self.pool.get('crm.lead') @@ -62,12 +64,13 @@ class crm_lead2opportunity_partner(osv.osv_memory): ids = [] if partner_id: + # Search for opportunities that have the same partner and that arent done or cancelled ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), ('type', '=', 'opportunity'), '!', ('state', 'in', ['done', 'cancel'])]) if ids: opportunities.append(ids[0]) if not partner_id: if email: - # Find email of existing opportunity matches the email_from of the lead + # Find email of existing opportunity matching the email_from of the lead cr.execute("""select id from crm_lead where type='opportunity' and substring(email_from from '([^ ,<@]+@[^> ,]+)') in (%s)""" % (','.join(email))) ids = map(lambda x:x[0], cr.fetchall()) @@ -83,12 +86,11 @@ class crm_lead2opportunity_partner(osv.osv_memory): if 'opportunity_ids' in fields: res.update({'opportunity_ids': opportunities}) - return res def view_init(self, cr, uid, fields, context=None): """ - This function checks for precondition before wizard executes + Check some preconditions before the wizard executes. """ if context is None: context = {} @@ -110,7 +112,7 @@ class crm_lead2opportunity_partner(osv.osv_memory): partner_id = partner_ids_map.get(lead_id, False) # FIXME: cannot pass user_ids as the salesman allocation only works in batch res = lead.convert_opportunity(cr, uid, [lead_id], partner_id, [], team_id, context=context) - # FIXME: must perform salesman allocation in batch separately here + # FIXME: must perform salesman allocation in batch separately here user_ids = vals.get('user_ids', False) if user_ids: lead.allocate_salesman(cr, uid, lead_ids, user_ids, team_id=team_id, context=context) @@ -136,9 +138,10 @@ class crm_lead2opportunity_partner(osv.osv_memory): def action_apply(self, cr, uid, ids, context=None): """ - This converts lead to opportunity and opens Opportunity view + Convert lead to opportunity or merge lead and opportunity and open + the freshly created opportunity view. """ - if not context: + if context is None: context = {} lead = self.pool.get('crm.lead') lead_ids = context.get('active_ids', []) diff --git a/addons/crm/wizard/crm_lead_to_opportunity_view.xml b/addons/crm/wizard/crm_lead_to_opportunity_view.xml index ef8acf95b58..3390ad8f547 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity_view.xml +++ b/addons/crm/wizard/crm_lead_to_opportunity_view.xml @@ -8,14 +8,8 @@ <field name="arch" type="xml"> <form string="Convert to Opportunity" version="7.0"> <group> - <field name="action" class="oe_inline"/> - <field name="partner_id" - attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}" - class="oe_inline"/> - </group> - <group> <field name="name" class="oe_inline"/> - <field name="opportunity_ids" attrs="{'invisible': [('name', '=', 'convert')]}"> + <field name="opportunity_ids" attrs="{'invisible': [('name', '!=', 'merge')]}"> <tree> <field name="name"/> <field name="partner_id"/> @@ -24,6 +18,12 @@ </tree> </field> </group> + <group attrs="{'invisible': [('name', '!=', 'convert')]}"> + <field name="action" class="oe_inline"/> + <field name="partner_id" + attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}" + class="oe_inline"/> + </group> <footer> <button name="action_apply" string="Create Opportunity" type="object" class="oe_highlight"/> or @@ -38,23 +38,28 @@ <field name="model">crm.lead2opportunity.partner.mass</field> <field name="arch" type="xml"> <form string="Convert to Opportunity" version="7.0"> - <group string="Conversion Options"> - <field name="action"/> - <field name="partner_id" attrs="{'invisible':[('action','!=','exist')],'required': [('action', '=', 'exist')]}"/> - <field name="name"/> + <separator string="Conversion Options"/> + <group> + <field name="name" class="oe_inline"/> + </group> + <group attrs="{'invisible': [('name', '!=', 'convert')]}"> + <field name="action" class="oe_inline"/> + <field name="partner_id" + attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}" + class="oe_inline"/> </group> - <group string="Select Opportunities" attrs="{'invisible': [('name', '=', 'convert')]}"> - <field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}"> - <tree> - <field name="name" /> - <field name="partner_id" /> - <field name="user_id" /> - <field name="section_id" /> - </tree> - </field> + <group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}"> + <field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}"> + <tree> + <field name="name" /> + <field name="partner_id" /> + <field name="user_id" /> + <field name="section_id" /> + </tree> + </field> </group> - - <group string="Assign opportunities to"> + + <group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}"> <field name="section_id" /> <field name="user_ids" colspan="4"> <tree> @@ -62,14 +67,14 @@ </tree> </field> </group> - + <footer> <button name="mass_convert" string="Convert to Opportunities" type="object" class="oe_highlight"/> or <button string="Cancel" class="oe_link" special="cancel"/> - </footer> + </footer> </form> - </field> + </field> </record> <record id="action_crm_lead2opportunity_partner" model="ir.actions.act_window"> @@ -83,10 +88,11 @@ <act_window id="action_crm_send_mass_convert" multi="True" - key2="client_action_multi" name="Convert opportunities" + key2="client_action_multi" name="Convert to opportunities" res_model="crm.lead2opportunity.partner.mass" src_model="crm.lead" view_mode="form" target="new" view_type="form" context="{'mass_convert' : True}" view_id="view_crm_lead2opportunity_partner_mass"/> + </data> </openerp> diff --git a/addons/crm/wizard/crm_merge_opportunities.py b/addons/crm/wizard/crm_merge_opportunities.py index 381bb9871ea..edfefb5e5bc 100644 --- a/addons/crm/wizard/crm_merge_opportunities.py +++ b/addons/crm/wizard/crm_merge_opportunities.py @@ -27,6 +27,8 @@ class crm_merge_opportunity(osv.osv_memory): _description = 'Merge two Opportunities' def action_merge(self, cr, uid, ids, context=None): + if context is None: + context = {} lead = self.pool.get('crm.lead') record = self.browse(cr, uid, ids[0], context=context) opportunities = record.opportunity_ids @@ -38,7 +40,7 @@ class crm_merge_opportunity(osv.osv_memory): return lead.redirect_opportunity_view(cr, uid, merge_id, context=context) _columns = { - 'opportunity_ids' : fields.many2many('crm.lead', 'merge_opportunity_rel', 'merge_id', 'opportunity_id', 'Opportunities', domain=[('type', '=', 'opportunity')]), + 'opportunity_ids': fields.many2many('crm.lead', rel='merge_opportunity_rel', id1='merge_id', id2='opportunity_id', string='Leads/Opportunities'), } def default_get(self, cr, uid, fields, context=None): diff --git a/addons/crm/wizard/crm_merge_opportunities_view.xml b/addons/crm/wizard/crm_merge_opportunities_view.xml index 3a0b11c4255..4b035200737 100644 --- a/addons/crm/wizard/crm_merge_opportunities_view.xml +++ b/addons/crm/wizard/crm_merge_opportunities_view.xml @@ -3,13 +3,12 @@ <data> <!-- Merge Opportunities --> - <record model="ir.ui.view" id="merge_opportunity_form"> <field name="name">crm.merge.opportunity.form</field> <field name="model">crm.merge.opportunity</field> <field name="arch" type="xml"> - <form string="Merge Opportunities" version="7.0"> - <separator string="Select Opportunities"/> + <form string="Merge Leads/Opportunities" version="7.0"> + <separator string="Select Leads/Opportunities"/> <field name="opportunity_ids"> <tree> <field name="name"/> @@ -19,7 +18,7 @@ </tree> </field> <footer> - <button name="action_merge" type="object" string="_Merge" class="oe_highlight"/> + <button name="action_merge" type="object" string="Merge" class="oe_highlight"/> or <button string="Cancel" class="oe_link" special="cancel"/> </footer> @@ -27,10 +26,9 @@ </field> </record> - <!-- Merge Opportunities action --> - + <!-- Merge leads/opportunities action --> <record model="ir.actions.act_window" id="merge_opportunity_act"> - <field name="name">Merge Opportunities</field> + <field name="name">Merge leads/opportunities</field> <field name="res_model">crm.merge.opportunity</field> <field name="view_type">form</field> <field name="view_mode">form</field> @@ -40,7 +38,7 @@ <act_window id="action_merge_opportunities" multi="True" - key2="client_action_multi" name="Merge Opportunities" + key2="client_action_multi" name="Merge leads/opportunities" res_model="crm.merge.opportunity" src_model="crm.lead" view_mode="form" target="new" view_type="form"/> diff --git a/addons/crm_claim/crm_claim_view.xml b/addons/crm_claim/crm_claim_view.xml index 820fb38e58a..b319339bcfd 100644 --- a/addons/crm_claim/crm_claim_view.xml +++ b/addons/crm_claim/crm_claim_view.xml @@ -181,6 +181,7 @@ </group> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -241,7 +242,7 @@ <attribute name="invisible">False</attribute> </xpath> <xpath expr="//page[@name='page_history']" position="inside"> - <group name="grp_claim" string="Claim"> + <group name="grp_claim" string="Claims"> <field name="claims_ids" colspan="4" nolabel="1"> <tree string="Partners Claim" editable="bottom"> <field name="name"/> diff --git a/addons/crm_helpdesk/crm_helpdesk_view.xml b/addons/crm_helpdesk/crm_helpdesk_view.xml index 5399c9880ca..d2be33e4584 100644 --- a/addons/crm_helpdesk/crm_helpdesk_view.xml +++ b/addons/crm_helpdesk/crm_helpdesk_view.xml @@ -61,10 +61,6 @@ on_change="onchange_partner_id(partner_id, email_from)" /> <field name="email_from"/> - <button name="remind_partner" - states="open,pending" - string="Send Reminder" type="object" - icon="gtk-go-forward" /> </group> <group string="Categorization"> <field name="priority"/> @@ -99,6 +95,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/crm_profiling/crm_profiling_view.xml b/addons/crm_profiling/crm_profiling_view.xml index 1e894b456c0..dacbca0b3c6 100644 --- a/addons/crm_profiling/crm_profiling_view.xml +++ b/addons/crm_profiling/crm_profiling_view.xml @@ -124,7 +124,7 @@ <field name="inherit_id" ref="base.view_partner_form"/> <field name="arch" type="xml"> <notebook position="inside"> - <page string="Profiling"> + <page string="Profiling" groups="base.group_user"> <button string="Use a questionnaire" name="%(action_open_questionnaire)d" type="action" colspan="1" icon="gtk-justify-fill" /> diff --git a/addons/crm_profiling/security/ir.model.access.csv b/addons/crm_profiling/security/ir.model.access.csv index 24fd7009c6a..71149a912ee 100644 --- a/addons/crm_profiling/security/ir.model.access.csv +++ b/addons/crm_profiling/security/ir.model.access.csv @@ -1,6 +1,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_crm_profiling_answer_salesman,crm_profiling.answer.salesman,model_crm_profiling_answer,base.group_sale_salesman,1,1,1,0
access_crm_profiling_answer_manager,crm_profiling.answer.manager,model_crm_profiling_answer,base.group_sale_manager,1,1,1,1
+access_crm_profiling_answer_user,crm_profiling.answer.user,model_crm_profiling_answer,base.group_user,1,0,0,0
access_crm_profiling_question_salesman,crm_profiling.question.salesman,model_crm_profiling_question,base.group_sale_salesman,1,1,1,0
access_crm_profiling_question_manager,crm_profiling.question.manager,model_crm_profiling_question,base.group_sale_manager,1,1,1,1
access_crm_profiling_questionnarie_salesman,crm_profiling.questionnarie.salesman,model_crm_profiling_questionnaire,base.group_sale_salesman,1,1,1,0
diff --git a/addons/delivery/delivery_view.xml b/addons/delivery/delivery_view.xml index deb59983ef2..4ac1ab12573 100644 --- a/addons/delivery/delivery_view.xml +++ b/addons/delivery/delivery_view.xml @@ -90,7 +90,7 @@ </record> <record id="action_delivery_carrier_form" model="ir.actions.act_window"> - <field name="name">Delivery Method</field> + <field name="name">Delivery Methods</field> <field name="type">ir.actions.act_window</field> <field name="res_model">delivery.carrier</field> <field name="view_type">form</field> @@ -230,6 +230,10 @@ <field name="carrier_tracking_ref"/> <field name="number_of_packages"/> </field> + <field name="company_id" position="before"> + <field name="weight"/> + <field name="weight_net" groups="base.group_no_one"/> + </field> </field> </record> @@ -307,13 +311,26 @@ <record id="view_delivery_order_inherit_stock" model="ir.ui.view"> <field name="name">stock.picking.out.form</field> <field name="model">stock.picking.out</field> - <field name="inherit_id" ref="stock.view_picking_out_form"/> + <field name="inherit_id" ref="stock.view_picking_form"/> <field name="arch" type="xml"> - <xpath expr="/form/header//button[@string='Create Invoice/Refund']" position="after"> - <button name="%(report_shipping)d" string="Delivery Order" states="done" type="action" icon="gtk-print"/> + <xpath expr="/form/header//button[@name='action_process']" position="after"> + <button name="%(report_shipping)d" string="Print Delivery Order" states="confirmed,assigned" type="action"/> + <button name="%(report_shipping)d" string="Print Delivery Order" states="done" type="action" class="oe_highlight"/> </xpath> </field> </record> - + + <record id="view_picking_withcarrier_in_form" model="ir.ui.view"> + <field name="name">delivery.stock.picking_withcarrier.in.form.view</field> + <field name="type">form</field> + <field name="model">stock.picking.in</field> + <field name="inherit_id" ref="stock.view_picking_form"/> + <field name="arch" type="xml"> + <field name="company_id" position="before"> + <field name="weight"/> + <field name="weight_net" groups="base.group_no_one"/> + </field> + </field> + </record> </data> </openerp> diff --git a/addons/delivery/stock.py b/addons/delivery/stock.py index f4d133e967a..c3ce236dc2e 100644 --- a/addons/delivery/stock.py +++ b/addons/delivery/stock.py @@ -202,5 +202,30 @@ class stock_picking_out(osv.osv): 'carrier_tracking_ref': fields.char('Carrier Tracking Ref', size=32), 'number_of_packages': fields.integer('Number of Packages'), } +stock_picking_out() + +class stock_picking_in(osv.osv): + _inherit = 'stock.picking.in' + + def _cal_weight(self, cr, uid, ids, name, args, context=None): + return self.pool.get('stock.picking')._cal_weight(cr, uid, ids, name, args, context=context) + + def _get_picking_line(self, cr, uid, ids, context=None): + return self.pool.get('stock.picking')._get_picking_line(cr, uid, ids, context=context) + + _columns = { + 'weight': fields.function(_cal_weight, type='float', string='Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight', + store={ + 'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 20), + 'stock.move': (_get_picking_line, ['product_id','product_qty','product_uom','product_uos_qty'], 20), + }), + 'weight_net': fields.function(_cal_weight, type='float', string='Net Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight', + store={ + 'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 20), + 'stock.move': (_get_picking_line, ['product_id','product_qty','product_uom','product_uos_qty'], 20), + }), + } +stock_picking_in() + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/document_ftp/wizard/ftp_browse.py b/addons/document_ftp/wizard/ftp_browse.py index b1120bc6511..8260424d653 100644 --- a/addons/document_ftp/wizard/ftp_browse.py +++ b/addons/document_ftp/wizard/ftp_browse.py @@ -39,7 +39,7 @@ class document_ftp_browse(osv.osv_memory): data_pool = self.pool.get('ir.model.data') aid = data_pool._get_id(cr, uid, 'document_ftp', 'action_document_browse') aid = data_pool.browse(cr, uid, aid, context=context).res_id - ftp_url = self.pool.get('ir.actions.url').browse(cr, uid, aid, context=context) + ftp_url = self.pool.get('ir.actions.act_url').browse(cr, uid, aid, context=context) url = ftp_url.url and ftp_url.url.split('ftp://') or [] if url: url = url[1] diff --git a/addons/document_ftp/wizard/ftp_configuration.py b/addons/document_ftp/wizard/ftp_configuration.py index 5da2ddf11e8..d0679973c5a 100644 --- a/addons/document_ftp/wizard/ftp_configuration.py +++ b/addons/document_ftp/wizard/ftp_configuration.py @@ -44,7 +44,7 @@ class document_ftp_configuration(osv.osv_memory): # Update the action for FTP browse. aid = data_pool._get_id(cr, uid, 'document_ftp', 'action_document_browse') aid = data_pool.browse(cr, uid, aid, context=context).res_id - self.pool.get('ir.actions.url').write(cr, uid, [aid], + self.pool.get('ir.actions.act_url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost:8021')+'/' + cr.dbname+'/'}) document_ftp_configuration() diff --git a/addons/document_ftp/wizard/ftp_configuration_view.xml b/addons/document_ftp/wizard/ftp_configuration_view.xml index 36fb80c51af..9e05fba986e 100644 --- a/addons/document_ftp/wizard/ftp_configuration_view.xml +++ b/addons/document_ftp/wizard/ftp_configuration_view.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <openerp> <data> - <record model="ir.actions.url" id="action_document_browse"> + <record model="ir.actions.act_url" id="action_document_browse"> <field name="name">Browse Files</field> <field name="url">ftp://localhost:8021/</field> </record> diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml index 40019513126..f786592d06c 100644 --- a/addons/event/event_view.xml +++ b/addons/event/event_view.xml @@ -196,6 +196,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -246,12 +247,12 @@ <t t-name="kanban-box"> <div class="oe_module_vignette"> <a type="open" class="oe_module_icon"> - <div class="oe_event_date "><t t-esc="record.date_begin.raw_value.getDate()"/></div> - <div class="oe_event_month_year"> - <t t-esc="record.date_begin.raw_value.toString('MMM')"/> - <t t-esc="record.date_begin.raw_value.getFullYear()"/> - </div> - <div class="oe_event_time"><t t-esc="record.date_begin.raw_value.toString('hh:mm tt')"/></div> + <div class="oe_event_date "><t t-esc="record.date_begin.raw_value.getDate()"/></div> + <div class="oe_event_month_year"> + <t t-esc="record.date_begin.raw_value.toString('MMM')"/> + <t t-esc="record.date_begin.raw_value.getFullYear()"/> + </div> + <div class="oe_event_time"><t t-esc="record.date_begin.raw_value.toString('hh:mm tt')"/></div> </a> <div class="oe_module_desc"> <h4><a type="open"><field name="name"/></a></h4> @@ -271,10 +272,10 @@ </p> <t t-if="record.register_avail.raw_value != 0"> <t t-if="!record.is_subscribed.raw_value"> - <input t-att-id="record.id.raw_value" type="text" name="subscribe" class="no_of_seats" value="1" onchange="document.getElementById('btn_sub' +this.id).setAttribute('data-context',JSON.stringify({'ticket':this.value}))"/> - <button t-att-id="'btn_sub'+record.id.raw_value" type="object" name="subscribe_to_event" class="oe_mail_button_follow"> - <span >Subscribe</span> - </button> + <input t-att-id="record.id.raw_value" type="text" name="subscribe" class="no_of_seats" value="1" onchange="document.getElementById('btn_sub' +this.id).setAttribute('data-context',JSON.stringify({'ticket':this.value}))"/> + <button t-att-id="'btn_sub'+record.id.raw_value" type="object" name="subscribe_to_event" class="oe_mail_button_follow"> + <span >Subscribe</span> + </button> </t> </t> <t t-if="record.is_subscribed.raw_value"> @@ -478,6 +479,7 @@ </group> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/event/res_partner_view.xml b/addons/event/res_partner_view.xml index 8e5ff480c2c..c3e8073a7ae 100644 --- a/addons/event/res_partner_view.xml +++ b/addons/event/res_partner_view.xml @@ -16,7 +16,7 @@ <attribute name="invisible">False</attribute> </xpath> <xpath expr="//page[@name='page_history']" position="inside"> - <group name="grp_event" string="Event"> + <group name="grp_event" string="Events"> <field name="event_ids" colspan="4" nolabel="1"> <tree string="Events"> <field name="name" string="Event"/> @@ -24,7 +24,7 @@ </tree> </field> </group> - <group name="grp_registration" string="Registration"> + <group name="grp_registration" string="Registrations"> <field name="event_registration_ids" colspan="4" nolabel="1"> <tree string="Events Registration"> <field name="event_begin_date" string="Date"/> diff --git a/addons/fetchmail/fetchmail.py b/addons/fetchmail/fetchmail.py index 70a05c6bd0e..e0023697594 100644 --- a/addons/fetchmail/fetchmail.py +++ b/addons/fetchmail/fetchmail.py @@ -195,7 +195,7 @@ openerp_mailgate.py -u %(uid)d -p PASSWORD -o %(model)s -d %(dbname)s --host=HOS strip_attachments=(not server.attach), context=context) if res_id and server.action_id: - action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]}) + action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id], 'active_model': context.get("thread_model", False)}) imap_server.store(num, '+FLAGS', '\\Seen') cr.commit() count += 1 @@ -220,7 +220,7 @@ openerp_mailgate.py -u %(uid)d -p PASSWORD -o %(model)s -d %(dbname)s --host=HOS strip_attachments=(not server.attach), context=context) if res_id and server.action_id: - action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]}) + action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id], 'active_model': context.get("thread_model", False)}) pop_server.dele(num) cr.commit() _logger.info("fetched/processed %s email(s) on %s server %s", numMsgs, server.type, server.name) diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml index 6e23a714848..6253205eb7e 100644 --- a/addons/hr/hr_view.xml +++ b/addons/hr/hr_view.xml @@ -205,25 +205,6 @@ </field> </record> - <record id="open_view_employee_list_my_kanban" model="ir.actions.act_window.view"> - <field name="sequence" eval="0"/> - <field name="view_mode">kanban</field> - <field name="act_window_id" ref="open_view_employee_list_my"/> - </record> - <record id="open_view_employee_list_my_tree2" model="ir.actions.act_window.view"> - <field name="sequence" eval="1"/> - <field name="view_mode">tree</field> - <field name="view_id" ref="view_employee_tree"/> - <field name="act_window_id" ref="open_view_employee_list_my"/> - </record> - - <record id="open_view_employee_list_my_form2" model="ir.actions.act_window.view"> - <field name="sequence" eval="2"/> - <field name="view_mode">form</field> - <field name="view_id" ref="view_employee_form"/> - <field name="act_window_id" ref="open_view_employee_list_my"/> - </record> - <menuitem action="open_view_employee_list_my" id="menu_open_view_employee_list_my" sequence="3" parent="menu_hr_main"/> <record id="ir_ui_view_sc_employee" model="ir.ui.view_sc"> @@ -372,14 +353,14 @@ <field name="company_id" widget="selection" groups="base.group_multi_company"/> </group> </group> - <notebook> - <page string="Description"> - <label for="description"/> - <field name="description"/> - <label for="requirements"/> - <field name="requirements"/> - </page> - </notebook> + <div> + <label for="description"/> + <field name="description"/> + </div> + <div> + <label for="requirements"/> + <field name="requirements"/> + </div> </sheet> </form> </field> diff --git a/addons/hr_evaluation/hr_evaluation_view.xml b/addons/hr_evaluation/hr_evaluation_view.xml index ca90e98e516..fb9d287ea77 100644 --- a/addons/hr_evaluation/hr_evaluation_view.xml +++ b/addons/hr_evaluation/hr_evaluation_view.xml @@ -178,7 +178,7 @@ <field nolabel="1" name="survey_request_ids"> <form string="Interview Appraisal" version="7.0"> <div class="oe_right oe_button_box"> - <button name="%(survey.action_view_survey_question_message)d" string="Answer Survey" type="action" states="waiting_answer,done,cancel" icon="gtk-execute" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0, 'active' : response,'request' : True, 'object' : 'hr.evaluation.interview', 'cur_id' : active_id}" attrs="{'readonly':[('survey_id','=',False)]}"/> + <button name="%(survey.action_view_survey_question_message)d" string="Answer Survey" type="action" states="waiting_answer" icon="gtk-execute" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0, 'active' : response,'request' : True, 'object' : 'hr.evaluation.interview', 'cur_id' : active_id}" attrs="{'readonly':[('survey_id','=',False)]}"/> <button name="%(survey.survey_browse_response)d" string="Print Interview" type="action" states="done" icon="gtk-print" context="{'survey_id': survey_id, 'response_id' : [response], 'response_no':0,}" attrs="{'readonly':[('response','=',False)]}" /> </div> <group> @@ -333,7 +333,7 @@ <field name="user_id" string="Interviewer"/> <field name="user_to_review_id"/> <field name="response" readonly="1" invisible="True"/> - <button name="%(survey.action_view_survey_question_message)d" string="Answer Survey" type="action" states="waiting_answer,done,cancel" icon="gtk-execute" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0, 'active' : response, 'request' : True, 'object' : 'hr.evaluation.interview', 'cur_id' : active_id}" attrs="{'readonly':[('survey_id','=',False)]}"/> + <button name="%(survey.action_view_survey_question_message)d" string="Answer Survey" type="action" states="waiting_answer" icon="gtk-execute" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0, 'active' : response, 'request' : True, 'object' : 'hr.evaluation.interview', 'cur_id' : active_id}" attrs="{'readonly':[('survey_id','=',False)]}"/> <button name="action_print_survey" string="Print Survey" type="object" icon="gtk-print" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0}" 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"/> diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml index 388e5262f6e..f35547927fa 100644 --- a/addons/hr_expense/hr_expense_view.xml +++ b/addons/hr_expense/hr_expense_view.xml @@ -139,6 +139,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 347e2b17ca5..0a2054b14d2 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -121,6 +121,7 @@ </group> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -158,6 +159,7 @@ <field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..."/> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/hr_payroll/hr_payroll.py b/addons/hr_payroll/hr_payroll.py index 08dacc9a951..b56b49d3d99 100644 --- a/addons/hr_payroll/hr_payroll.py +++ b/addons/hr_payroll/hr_payroll.py @@ -317,7 +317,10 @@ class hr_payslip(osv.osv): 'company_id': company_id, 'period_id': False, 'basic_before_leaves': 0.0, - 'basic_amount': 0.0 + 'basic_amount': 0.0, + 'number': '', + 'payslip_run_id': False, + 'paid': False, }) return super(hr_payslip, self).copy(cr, uid, id, default, context=context) diff --git a/addons/hr_payroll/hr_payroll_view.xml b/addons/hr_payroll/hr_payroll_view.xml index 3ba9467395e..c87d36de591 100644 --- a/addons/hr_payroll/hr_payroll_view.xml +++ b/addons/hr_payroll/hr_payroll_view.xml @@ -234,7 +234,7 @@ </h2> </div> <group col="4"> - <field name="contract_id" domain="[('employee_id','=',employee_id)]" on_change="onchange_contract_id(date_from, date_to, employee_id, contract_id)" context="{'default_employee_id': employee_id}"/> + <field name="contract_id" domain="[('employee_id','=',employee_id),('date_start','<=',date_to),'|',('date_end','>=',date_from),('date_end','=',False)]" on_change="onchange_contract_id(date_from, date_to, employee_id, contract_id)" context="{'default_employee_id': employee_id}"/> <field name="number"/> <field name="struct_id" attrs="{'required':[('contract_id','<>',False)]}"/> <field name="name"/> diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 08e5f31a2d2..32c26573e8c 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -183,6 +183,7 @@ <field name="description" placeholder="Feedback of interviews..."/> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/hr_recruitment/security/ir.model.access.csv b/addons/hr_recruitment/security/ir.model.access.csv index 086e63e542b..2d0b85faf70 100644 --- a/addons/hr_recruitment/security/ir.model.access.csv +++ b/addons/hr_recruitment/security/ir.model.access.csv @@ -8,5 +8,5 @@ access_survey_hr_user,survey.hr.user,survey.model_survey,base.group_hr_user,1,1, access_crm_meeting_hruser,crm.meeting.hruser,base_calendar.model_crm_meeting,base.group_hr_user,1,1,1,1
access_hr_recruitment_source_hr_officer,hr.recruitment.source,model_hr_recruitment_source,base.group_hr_user,1,1,1,1
access_hr_recruitment_source_all,hr.recruitment.source,model_hr_recruitment_source,,1,0,0,0
-access_hr_applicant_category,hr.applicant_category,model_hr_applicant_category,,1,0,0,0
+access_hr_applicant_category,hr.applicant_category,model_hr_applicant_category,,1,1,1,0
access_hr_applicant_category_manager,hr.applicant_category,model_hr_applicant_category,base.group_hr_manager,1,1,1,1
diff --git a/addons/hr_timesheet/hr_timesheet_view.xml b/addons/hr_timesheet/hr_timesheet_view.xml index 43b677d7978..c81cda8493b 100644 --- a/addons/hr_timesheet/hr_timesheet_view.xml +++ b/addons/hr_timesheet/hr_timesheet_view.xml @@ -116,10 +116,12 @@ <field name="search_view_id" ref="hr_timesheet_line_search"/> <field name="help" type="html"> <p class="oe_view_nocontent_create"> - Click to your timesheets. + Click to record your timesheets. </p><p> - Through this menu you can register and follow your workings - hours by project every day. + You can register and track your workings hours by project every + day. Every time spent on a project will become a cost in the + analytic accounting and can be re-invoiced to customers if + required. </p> </field> </record> diff --git a/addons/hr_timesheet/security/ir.model.access.csv b/addons/hr_timesheet/security/ir.model.access.csv index d5f89098a69..069a5a9c170 100644 --- a/addons/hr_timesheet/security/ir.model.access.csv +++ b/addons/hr_timesheet/security/ir.model.access.csv @@ -1,8 +1,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_analytic_timesheet,hr.analytic.timesheet,model_hr_analytic_timesheet,base.group_hr_user,1,1,1,1
-access_hr_analytic_timesheet_employee,hr.analytic.timesheet,model_hr_analytic_timesheet,base.group_user,1,1,1,0
+access_hr_analytic_timesheet_employee,hr.analytic.timesheet,model_hr_analytic_timesheet,base.group_user,1,1,1,1
access_hr_account_analytic_line,account.account.analytic.line,account.model_account_analytic_line,base.group_hr_user,1,1,1,1
-access_hr_account_analytic_line_employee,account.account.analytic.line employee,account.model_account_analytic_line,base.group_user,1,1,1,0
+access_hr_account_analytic_line_employee,account.account.analytic.line employee,account.model_account_analytic_line,base.group_user,1,1,1,1
access_account_analytic_journal,account.account.analytic.journal,account.model_account_analytic_journal,base.group_hr_user,1,1,1,1
access_product_product_user,product.product user,product.model_product_product,base.group_hr_user,1,1,1,1
access_product_template_hr_timesheet,product.template.hr.timesheet,product.model_product_template,base.group_hr_user,1,1,1,1
diff --git a/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report.py b/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report.py index 6b6dd9f6e52..7af9603ebd8 100644 --- a/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report.py +++ b/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report.py @@ -31,7 +31,7 @@ class report_timesheet_line(osv.osv): 'user_id': fields.many2one('res.users', 'User', readonly=True), 'date': fields.date('Date', readonly=True), 'day': fields.char('Day', size=128, readonly=True), - 'quantity': fields.float('Quantity', readonly=True), + 'quantity': fields.float('Time', readonly=True), 'cost': fields.float('Cost', readonly=True), 'product_id': fields.many2one('product.product', 'Product',readonly=True), 'account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True), @@ -83,7 +83,7 @@ class report_timesheet_user(osv.osv): _columns = { 'name': fields.char('Year',size=64,required=False, readonly=True), 'user_id':fields.many2one('res.users', 'User', readonly=True), - 'quantity': fields.float('Quantity', readonly=True), + 'quantity': fields.float('Time', readonly=True), 'cost': fields.float('Cost', readonly=True), 'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')],'Month',readonly=True), @@ -117,7 +117,7 @@ class report_timesheet_account(osv.osv): 'name': fields.char('Year',size=64,required=False, readonly=True), 'user_id':fields.many2one('res.users', 'User', readonly=True), 'account_id':fields.many2one('account.analytic.account', 'Analytic Account', readonly=True), - 'quantity': fields.float('Quantity', readonly=True), + 'quantity': fields.float('Time', readonly=True), 'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')],'Month',readonly=True), @@ -151,7 +151,7 @@ class report_timesheet_account_date(osv.osv): 'name': fields.char('Year',size=64,required=False, readonly=True), 'user_id':fields.many2one('res.users', 'User', readonly=True), 'account_id':fields.many2one('account.analytic.account', 'Analytic Account', readonly=True), - 'quantity': fields.float('Quantity', readonly=True), + 'quantity': fields.float('Time', readonly=True), 'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')],'Month',readonly=True), } @@ -185,7 +185,7 @@ class report_timesheet_invoice(osv.osv): 'user_id':fields.many2one('res.users', 'User', readonly=True), 'account_id':fields.many2one('account.analytic.account', 'Project', readonly=True), 'manager_id':fields.many2one('res.users', 'Manager', readonly=True), - 'quantity': fields.float('Quantity', readonly=True), + 'quantity': fields.float('Time', readonly=True), 'amount_invoice': fields.float('To invoice', readonly=True) } _rec_name = 'user_id' diff --git a/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py b/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py index 76314e1986a..0014ac685b2 100644 --- a/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py +++ b/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py @@ -79,17 +79,24 @@ class account_analytic_line(osv.osv): curr_invoice = { 'name': time.strftime('%d/%m/%Y')+' - '+account.name, 'partner_id': account.partner_id.id, + 'company_id': account.company_id.id, 'payment_term': partner.property_payment_term.id or False, 'account_id': partner.property_account_receivable.id, 'currency_id': account.pricelist_id.currency_id.id, 'date_due': date_due, 'fiscal_position': account.partner_id.property_account_position.id } - last_invoice = invoice_obj.create(cr, uid, curr_invoice, context=context) - invoices.append(last_invoice) - + context2 = context.copy() context2['lang'] = partner.lang + # set company_id in context, so the correct default journal will be selected + context2['force_company'] = curr_invoice['company_id'] + # set force_company in context so the correct product properties are selected (eg. income account) + context2['company_id'] = curr_invoice['company_id'] + + last_invoice = invoice_obj.create(cr, uid, curr_invoice, context=context2) + invoices.append(last_invoice) + cr.execute("SELECT product_id, to_invoice, sum(unit_amount), product_uom_id, name " \ "FROM account_analytic_line as line " \ "WHERE account_id = %s " \ @@ -115,11 +122,11 @@ class account_analytic_line(osv.osv): else: price = 0.0 + general_account = product.product_tmpl_id.property_account_income or product.categ_id.property_account_income_categ + if not general_account: + raise osv.except_osv(_("Configuration Error!"), _("Please define income account for product '%s'.") % product.name) taxes = product.taxes_id tax = fiscal_pos_obj.map_tax(cr, uid, account.partner_id.property_account_position, taxes) - account_id = product.product_tmpl_id.property_account_income.id or product.categ_id.property_account_income_categ.id - if not account_id: - raise osv.except_osv(_("Configuration Error!"), _("Please define income account for product '%s'.") % product.name) curr_line = { 'price_unit': price, 'quantity': qty, @@ -130,7 +137,7 @@ class account_analytic_line(osv.osv): 'product_id': product_id, 'invoice_line_tax_id': [(6,0,tax)], 'uos_id': uom, - 'account_id': account_id, + 'account_id': general_account.id, 'account_analytic_id': account.id, } diff --git a/addons/hr_timesheet_sheet/report/hr_timesheet_report.py b/addons/hr_timesheet_sheet/report/hr_timesheet_report.py index 49b18f75e4b..1fdcac9f7c6 100644 --- a/addons/hr_timesheet_sheet/report/hr_timesheet_report.py +++ b/addons/hr_timesheet_sheet/report/hr_timesheet_report.py @@ -43,7 +43,7 @@ class hr_timesheet_report(osv.osv): 'account_id': fields.many2one('account.analytic.account', 'Analytic Account',readonly=True), 'company_id': fields.many2one('res.company', 'Company',readonly=True), 'cost': fields.float('Cost',readonly=True, digits_compute=dp.get_precision('Account')), - 'quantity': fields.float('Quantity',readonly=True), + 'quantity': fields.float('Time',readonly=True), } def init(self, cr): diff --git a/addons/hr_timesheet_sheet/report/timesheet_report.py b/addons/hr_timesheet_sheet/report/timesheet_report.py index 38e19d708d4..3db7ff8feb2 100644 --- a/addons/hr_timesheet_sheet/report/timesheet_report.py +++ b/addons/hr_timesheet_sheet/report/timesheet_report.py @@ -52,7 +52,7 @@ class timesheet_report(osv.osv): ('draft','Draft'), ('confirm','Confirmed'), ('done','Done')], 'Status', readonly=True), - 'quantity': fields.float('#Quantity',readonly=True), + 'quantity': fields.float('Time',readonly=True), 'cost': fields.float('#Cost',readonly=True), } diff --git a/addons/idea/idea_view.xml b/addons/idea/idea_view.xml index eab3d03ceea..1b7cc5446b8 100644 --- a/addons/idea/idea_view.xml +++ b/addons/idea/idea_view.xml @@ -77,6 +77,7 @@ <field name="description"/> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/import_google/wizard/google_import_message_view.xml b/addons/import_google/wizard/google_import_message_view.xml index 158aff29bf3..d44bd8e26ec 100644 --- a/addons/import_google/wizard/google_import_message_view.xml +++ b/addons/import_google/wizard/google_import_message_view.xml @@ -10,6 +10,9 @@ <group> <field name='name'/> </group> + <footer> + <button string="Close" class="oe_highlight" special="cancel" /> + </footer> </form> </field> </record> diff --git a/addons/import_google/wizard/import_google.py b/addons/import_google/wizard/import_google.py index d231c186321..f8fc8991975 100644 --- a/addons/import_google/wizard/import_google.py +++ b/addons/import_google/wizard/import_google.py @@ -27,6 +27,7 @@ from dateutil import * from pytz import timezone from datetime import datetime import time +import base64 from osv import * from tools.translate import _ @@ -282,7 +283,7 @@ class google_import(import_framework): self.contact = self.gd_client.GetContactsFeed() while self.contact: for entry in self.contact.entry: - data = {} + data = {} data['id'] = entry.id.text name = tools.ustr(entry.title.text) if name == "None": @@ -290,6 +291,8 @@ class google_import(import_framework): data['name'] = name or _('Unknown') emails = ','.join(email.address for email in entry.email) data['email'] = emails + if self.gd_client.GetPhoto(entry): + data['image'] = base64.encodestring(self.gd_client.GetPhoto(entry)) if table == 'Contact': data.update({'customer': str(self.context.get('customer')), 'supplier': str(self.context.get('supplier'))}) @@ -322,6 +325,12 @@ class google_import(import_framework): 'name': value('company', fallback='name'), 'customer': 'customer', 'supplier': 'supplier', + 'city': 'city', + 'phone': 'phone', + 'mobile': 'mobile', + 'email': 'email', + 'image': 'image', + 'fax': 'fax', 'child_ids/id': ref(self.TABLE_ADDRESS, 'id'), } } diff --git a/addons/import_google/wizard/import_google_data.py b/addons/import_google/wizard/import_google_data.py index 2132d141e29..5ce1d160f3e 100644 --- a/addons/import_google/wizard/import_google_data.py +++ b/addons/import_google/wizard/import_google_data.py @@ -104,7 +104,7 @@ class synchronize_google(osv.osv_memory): _columns = { 'customer': fields.boolean('Customer', help="Check this box to set newly created partner as Customer."), 'supplier': fields.boolean('Supplier', help="Check this box to set newly created partner as Supplier."), - 'group_name': fields.selection(_get_group, "Group Name",help="Choose which group to import, By default it takes all."), + 'group_name': fields.selection(_get_group, "Group Name", help="Choose which group to import, By default it takes all."), 'calendar_name': fields.selection(_get_calendars, "Calendar Name"), } diff --git a/addons/import_google/wizard/import_google_data_view.xml b/addons/import_google/wizard/import_google_data_view.xml index f8e110e6a5d..e11b5aa2d08 100644 --- a/addons/import_google/wizard/import_google_data_view.xml +++ b/addons/import_google/wizard/import_google_data_view.xml @@ -7,7 +7,7 @@ <field name="arch" type="xml"> <form string="Import contacts from a google account" version="7.0"> <group colspan="4" col="4" width="430"> - <field name="group_name" colspan="4"/> + <field name="group_name" colspan="4" required="1"/> <newline/> <group colspan="4" col="4"> <separator string="Partner Status for this Group:" colspan="4"/> @@ -32,7 +32,7 @@ <field name="arch" type="xml"> <form string="Import Google Calendar Events" version="7.0"> <group colspan="4" col="4" width="320"> - <field name="calendar_name" /> + <field name="calendar_name" required="1"/> </group> <footer> <button name="import_google" string="_Import Events" type="object" class="oe_highlight" /> diff --git a/addons/l10n_be/wizard/l10n_be_partner_vat_listing.py b/addons/l10n_be/wizard/l10n_be_partner_vat_listing.py index fad6ca53f92..d15b300597e 100644 --- a/addons/l10n_be/wizard/l10n_be_partner_vat_listing.py +++ b/addons/l10n_be/wizard/l10n_be_partner_vat_listing.py @@ -70,7 +70,7 @@ class partner_vat(osv.osv_memory): FROM account_move_line l LEFT JOIN res_partner p ON l.partner_id = p.id LEFT JOIN account_tax_code c ON l.tax_code_id = c.id - WHERE c.code IN ('01','02','03','45','49') + WHERE c.code IN ('00','01','02','03','45','49') AND l.partner_id IN %s AND l.period_id IN %s GROUP BY l.partner_id, p.name, p.vat) AS sub1 diff --git a/addons/l10n_ch/account_wizard.py b/addons/l10n_ch/account_wizard.py index 7b80e3697ee..306207c612d 100644 --- a/addons/l10n_ch/account_wizard.py +++ b/addons/l10n_ch/account_wizard.py @@ -36,8 +36,9 @@ class WizardMultiChartsAccounts(osv.osv_memory): def execute(self, cr, uid, ids, context=None): """Override of code in order to be able to link journal with account in XML""" res = super(WizardMultiChartsAccounts, self).execute(cr, uid, ids, context) - path = addons.get_module_resource(os.path.join('l10n_ch','sterchi_chart','account_journal_rel.xml')) + path = addons.get_module_resource('l10n_ch','sterchi_chart','account_journal_rel.xml') tools.convert_xml_import(cr, 'l10n_ch', path, idref=None, mode='init', noupdate=True, report=None) + res.update({'type': 'ir.actions.act_window_close'}) return res WizardMultiChartsAccounts() diff --git a/addons/l10n_ch/bank.py b/addons/l10n_ch/bank.py index 9dd0caddf38..79dcb1a6834 100644 --- a/addons/l10n_ch/bank.py +++ b/addons/l10n_ch/bank.py @@ -48,7 +48,7 @@ class ResPartnerBank(osv.osv): 'dta_code': fields.char('DTA code', size=5), 'print_bank': fields.boolean('Print Bank on BVR'), 'print_account': fields.boolean('Print Account Number on BVR'), - 'acc_number': fields.char('Account/IBAN Number', size=64), + 'acc_number': fields.char('Account Number', size=64), 'my_bank': fields.boolean('Use my account to print BVR ?', help="Check to print BVR invoices"), } diff --git a/addons/l10n_ch/bank_view.xml b/addons/l10n_ch/bank_view.xml index da1badd7fe8..017f9bd1f9b 100644 --- a/addons/l10n_ch/bank_view.xml +++ b/addons/l10n_ch/bank_view.xml @@ -28,34 +28,6 @@ </field> </field> </record> - - <!-- res.partner.bank in res partner form--> - <!-- Partner -> Bank Details --> - - <record model="ir.ui.view" id="l10nch_view_res_partner_bank_add_groups"> - <field name="name">res.partner_bank.form.hide.f2</field> - <field name="model">res.partner</field> - <field name="inherit_id" ref="account.view_partner_property_form"/> - <field name="arch" type="xml"> - <field name="state" position="after"> - <newline/> - <separator colspan="4" string="Account Infos"/> - <group string="Bank account info" colspan="4" attrs="{'invisible': [('state','in', ['bvpost','bvrpost'])]}" > - <field name="acc_number" string="Account/IBAN Number" attrs="{'required': [('state','not in',['bvpost','bvrpost'])]}"/> - <field name="dta_code"/> - <newline/> - </group> - <newline/> - <group string="Postal account info" colspan="4" attrs="{'invisible': [('state','not in', ['bvpost','bvrpost','bvbank','bvrbank'])]}" > - <field name="post_number" attrs="{'required': [('state','in',['bvpost','bvrpost','bvrbank'])]}"/> - <newline/> - </group> - <separator colspan="4" string="Financial Institute Infos"/> - <field name="bank" /> - </field> - </field> - </record> - <!-- res.partner.bank base form--> <!-- Sales -> Configuration -> Address Book -> Bank accounts --> @@ -66,35 +38,21 @@ <field name="model">res.partner.bank</field> <field name="inherit_id" ref="base.view_partner_bank_form"/> <field name="arch" type="xml"> - <field name="state" position="after"> - <newline/> - - <separator colspan="4" string="Account Infos"/> - <newline/> - <group string="Bank account info" colspan="4" attrs="{'invisible': [('state','in', ['bvpost','bvrpost'])]}" > - <field name="acc_number" string="Account/IBAN Number" attrs="{'required': [('state','not in',['bvpost','bvrpost'])]}"/> - <field name="dta_code"/> - <newline/> - </group> - <newline/> - <group string="Postal account info" colspan="4" attrs="{'invisible': [('state','not in', ['bvpost','bvrpost','bvbank','bvrbank'])]}" > - <field name="post_number" attrs="{'required': [('state','in',['bvpost','bvrpost','bvrbank'])]}"/> - <newline/> - </group> - <separator colspan="4" string="Financial Institute Infos"/> - <newline/> - <group attrs="{'invisible': ['|', ('state', 'not in', ['bvrpost','bvrbank']), ('company_id', '=', False)]}" colspan="4"> - <field name="my_bank" attrs="{'invisible': [('company_id', '=', False)]}" /> - <group string="BVR print options" colspan="4" attrs="{'invisible': [('my_bank', '!=', True)]}" > - <field name="bvr_adherent_num" attrs="{'invisible': [('state','!=','bvrbank')]}"/> - <field name="print_bank"/> - <field name="print_account"/> - <newline/> - </group> + <xpath expr="//field[@name='acc_number']" position="attributes"> + <attribute name="attrs">{'required': [('state','not in',['bvpost','bvrpost'])]}</attribute> + </xpath> + <xpath expr="//field[@name='acc_number']" position="after"> + <field name="post_number" attrs="{'required': [('state','in',['bvpost','bvrpost','bvrbank'])], 'invisible': [('state','not in', ['bvpost','bvrpost','bvbank','bvrbank'])]}"/> + <field name="dta_code" attrs="{'invisible': [('state','in', ['bvpost','bvrpost'])]}"/> + <field name="my_bank" attrs="{'invisible': ['|', ('state', 'not in', ['bvrpost','bvrbank']), ('company_id', '=', False)]}" /> + </xpath> + <xpath expr="//group[@name='bank']" position="after"> + <group string="BVR print options" attrs="{'invisible': [('my_bank', '!=', True)]}" > + <field name="bvr_adherent_num" attrs="{'invisible': [('state','!=','bvrbank')]}"/> + <field name="print_bank"/> + <field name="print_account"/> </group> - <newline/> - <field name="bank" /> - </field> + </xpath> </field> </record> diff --git a/addons/l10n_in/l10n_in_private_chart.xml b/addons/l10n_in/l10n_in_private_chart.xml index f38f95750cf..45f5edb33d9 100644 --- a/addons/l10n_in/l10n_in_private_chart.xml +++ b/addons/l10n_in/l10n_in_private_chart.xml @@ -277,7 +277,7 @@ <field name="type">view</field> <field name="user_type" ref="account.data_account_type_liability"/> <field name="reconcile" eval="False"/> - <field name="parent_id" ref="a1"/> + <field name="parent_id" ref="a20"/> </record> <!-- <record model="account.account.template" id="290"> diff --git a/addons/l10n_ma/__openerp__.py b/addons/l10n_ma/__openerp__.py index 1648fb6b171..6cc213c8d2f 100644 --- a/addons/l10n_ma/__openerp__.py +++ b/addons/l10n_ma/__openerp__.py @@ -36,7 +36,6 @@ Seddik au cours du troisième trimestre 2010.""", 'website': 'http://www.kazacube.com', 'depends' : ['base', 'account'], 'data' : [ - 'security/compta_security.xml', 'security/ir.model.access.csv', 'account_type.xml', 'account_pcg_morocco.xml', diff --git a/addons/l10n_ma/security/compta_security.xml b/addons/l10n_ma/security/compta_security.xml deleted file mode 100644 index 8d511ae7154..00000000000 --- a/addons/l10n_ma/security/compta_security.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><openerp><data noupdate="0"> - - <record id="group_expert_comptable" model="res.groups"> - <field name="name">Finance / Expert Comptable </field> - <field name="users" eval="[(4, ref('base.user_root'))]"/> - </record> -</data> -</openerp> diff --git a/addons/l10n_nl/account_chart_netherlands.xml b/addons/l10n_nl/account_chart_netherlands.xml index 4c9fe63308b..48cf78f382b 100644 --- a/addons/l10n_nl/account_chart_netherlands.xml +++ b/addons/l10n_nl/account_chart_netherlands.xml @@ -3955,11 +3955,13 @@ <field name="code">4a</field> <field name="parent_id" ref="btw_code_vanuit_buitenland"/> <field name="name">Leveringen uit landen buiten de EU (invoer) (BTW)</field> + <field name="sign" eval="-1" /> </record> <record id="btw_code_4b" model="account.tax.code.template"> <field name="code">4b</field> <field name="parent_id" ref="btw_code_vanuit_buitenland"/> <field name="name">Verwerving van goederen uit landen binnen de EU (BTW)</field> + <field name="sign" eval="-1" /> </record> <!-- 5. Voorbelasting, kleineondernemersregeling, schatting en eindtotaal --> <record id="btw_code_voorbelasting" model="account.tax.code.template"> @@ -4100,6 +4102,7 @@ <field name="property_account_payable" ref="a_pay"/> <!-- crediteuren --> <field name="property_account_expense_categ" ref="a_expense"/><!-- aankoop grondstoffen --> <field name="property_account_income_categ" ref="a_sale"/> <!-- verkoop rekening --> + <field name="property_reserve_and_surplus_account" ref="a_9999"/> </record> <!-- BTW Template @@ -4325,7 +4328,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_I_6_1" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import binnen EU laag(1)</field> - <field eval="1.00" name="amount"/> + <field eval="-1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_I_6"/> <field name="account_collected_id" ref="vat_payable6"/> @@ -4337,7 +4340,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_I_6_2" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import binnen EU laag(2)</field> - <field eval="-1.00" name="amount"/> + <field eval="1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_I_6"/> <field name="account_collected_id" ref="vat_refund6"/> @@ -4360,7 +4363,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_I_19_1" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import binnen EU hoog(1)</field> - <field eval="1.00" name="amount"/> + <field eval="-1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_I_19"/> <field name="account_collected_id" ref="vat_payable19"/> @@ -4372,7 +4375,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_I_19_2" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import binnen EU hoog(2)</field> - <field eval="-1.00" name="amount"/> + <field eval="1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_I_19"/> <field name="account_collected_id" ref="vat_refund19"/> @@ -4395,7 +4398,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_I_overig_1" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import binnen EU overig(1)</field> - <field eval="1.00" name="amount"/> + <field eval="-1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_I_overig"/> <field name="account_collected_id" ref="vat_payable19"/> @@ -4407,7 +4410,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_I_overig_2" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import binnen EU overig(2)</field> - <field eval="-1.00" name="amount"/> + <field eval="1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_I_overig"/> <field name="account_collected_id" ref="vat_refund19"/> @@ -4458,7 +4461,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_E1_1" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import buiten EU laag(1)</field> - <field eval="1.00" name="amount"/> + <field eval="-1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_E1"/> <field name="account_collected_id" ref="vat_payable6"/> @@ -4470,7 +4473,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_E1_2" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import buiten EU laag(2)</field> - <field eval="-1.00" name="amount"/> + <field eval="1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_E1"/> <field name="account_collected_id" ref="vat_refund6"/> @@ -4493,7 +4496,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_E2_1" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import buiten EU hoog(1)</field> - <field eval="1.00" name="amount"/> + <field eval="-1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_E2"/> <field name="account_collected_id" ref="vat_payable19"/> @@ -4505,7 +4508,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_E2_2" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import buiten EU hoog(2)</field> - <field eval="-1.00" name="amount"/> + <field eval="1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_E2"/> <field name="account_collected_id" ref="vat_refund19"/> @@ -4528,7 +4531,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_E_overig_1" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import buiten EU overig(1)</field> - <field eval="1.00" name="amount"/> + <field eval="-1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_E_overig"/> <field name="account_collected_id" ref="vat_payable19"/> @@ -4540,7 +4543,7 @@ TODO rubriek 1c en 1d moeten nog, zijn variabel, dus BTW percentage moet door ge <record id="btw_E_overig_2" model="account.tax.template"> <field name="chart_template_id" ref="l10nnl_chart_template"/> <field name="name">Inkopen import buiten EU overig(2)</field> - <field eval="-1.00" name="amount"/> + <field eval="1.00" name="amount"/> <field name="type">percent</field> <field name="parent_id" ref="btw_E_overig"/> <field name="account_collected_id" ref="vat_refund19"/> diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py index b8f6c21ecf5..23417850f2a 100644 --- a/addons/mail/mail_followers.py +++ b/addons/mail/mail_followers.py @@ -19,6 +19,7 @@ # ############################################################################## +from openerp import SUPERUSER_ID from osv import osv from osv import fields import tools @@ -91,7 +92,7 @@ class mail_notification(osv.Model): :param browse_record message: mail.message to notify """ notify_pids = [] - for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context): + for partner in self.pool.get('res.partner').browse(cr, SUPERUSER_ID, partner_ids, context=context): # Do not send an email to the writer if partner.user_ids and partner.user_ids[0].id == uid: continue diff --git a/addons/mail/mail_group_menu.py b/addons/mail/mail_group_menu.py index 25b85bb7b22..707ece6a180 100644 --- a/addons/mail/mail_group_menu.py +++ b/addons/mail/mail_group_menu.py @@ -19,9 +19,10 @@ # ############################################################################## +from openerp import SUPERUSER_ID from osv import osv from osv import fields -from tools.translate import _ + class ir_ui_menu(osv.osv): """ Override of ir.ui.menu class. When adding mail_thread module, each @@ -38,13 +39,14 @@ class ir_ui_menu(osv.osv): def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): """ Override to take off menu entries (mail.group) the user is not - following. """ + following. Access are done using SUPERUSER_ID to avoid access + rights issues for an internal back-end algorithm. """ ids = super(ir_ui_menu, self).search(cr, uid, args, offset=0, limit=None, order=order, context=context, count=False) - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] follower_obj = self.pool.get('mail.followers') for menu in self.browse(cr, uid, ids, context=context): if menu.mail_group_id: - sub_ids = follower_obj.search(cr, uid, [ + sub_ids = follower_obj.search(cr, SUPERUSER_ID, [ ('partner_id', '=', partner_id), ('res_model', '=', 'mail.group'), ('res_id', '=', menu.mail_group_id.id) ], context=context) diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml index a551a549b06..94b9f8f9b0b 100644 --- a/addons/mail/mail_group_view.xml +++ b/addons/mail/mail_group_view.xml @@ -81,6 +81,7 @@ </group> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/> <field name="message_follower_ids" widget="mail_followers"/> diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 6bbfa9bc30e..e9a569a8192 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -19,13 +19,12 @@ # ############################################################################## -# import ast import base64 import logging import tools -from osv import osv -from osv import fields +from openerp import SUPERUSER_ID +from osv import osv, fields from tools.translate import _ _logger = logging.getLogger(__name__) @@ -134,7 +133,8 @@ class mail_mail(osv.Model): :return: True """ if mail.auto_delete: - mail.unlink() + # done with SUPERUSER_ID to avoid giving large unlink access rights + self.unlink(cr, SUPERUSER_ID, [mail.id], context=context) return True def send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None): diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 1d5d85e36e0..7d9c7b3085f 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -24,8 +24,9 @@ import openerp import tools from email.header import decode_header +from openerp import SUPERUSER_ID from operator import itemgetter -from osv import osv, fields +from osv import osv, orm, fields from tools.translate import _ _logger = logging.getLogger(__name__) @@ -62,14 +63,14 @@ class mail_message(osv.Model): continue try: result[message.id] = self._shorten_name(self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1]) - except openerp.exceptions.AccessDenied, e: + except (orm.except_orm, osv.except_osv): pass return result def _get_unread(self, cr, uid, ids, name, arg, context=None): """ Compute if the message is unread by the current user. """ res = dict((id, {'unread': False}) for id in ids) - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] notif_obj = self.pool.get('mail.notification') notif_ids = notif_obj.search(cr, uid, [ ('partner_id', 'in', [partner_id]), @@ -87,7 +88,7 @@ class mail_message(osv.Model): read_cond = '(read = false or read is null)' else: read_cond = 'read = true' - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] cr.execute("SELECT message_id FROM mail_notification "\ "WHERE partner_id = %%s AND %s" % read_cond, (partner_id,)) @@ -140,7 +141,8 @@ class mail_message(osv.Model): return [] def _get_default_author(self, cr, uid, context=None): - return self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + # remove context to avoid possible hack in browse with superadmin using context keys that could trigger a specific behavior + return self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=None).partner_id.id _defaults = { 'type': 'email', @@ -154,16 +156,17 @@ class mail_message(osv.Model): #------------------------------------------------------ def vote_toggle(self, cr, uid, ids, user_ids=None, context=None): - ''' Toggles voting ''' + ''' Toggles voting. Done as SUPERUSER_ID because of write access on + mail.message not always granted. ''' if not user_ids: user_ids = [uid] for message in self.read(cr, uid, ids, ['vote_user_ids'], context=context): for user_id in user_ids: has_voted = user_id in message.get('vote_user_ids') if not has_voted: - self.write(cr, uid, message.get('id'), {'vote_user_ids': [(4, user_id)]}, context=context) + self.write(cr, SUPERUSER_ID, message.get('id'), {'vote_user_ids': [(4, user_id)]}, context=context) else: - self.write(cr, uid, message.get('id'), {'vote_user_ids': [(3, user_id)]}, context=context) + self.write(cr, SUPERUSER_ID, message.get('id'), {'vote_user_ids': [(3, user_id)]}, context=context) return True #------------------------------------------------------ @@ -171,17 +174,31 @@ class mail_message(osv.Model): #------------------------------------------------------ def _message_dict_get(self, cr, uid, msg, context=None): - """ Return a dict representation of the message browse record. """ + """ Return a dict representation of the message browse record. A read + is performed to because of access rights issues (reading many2one + fields allow to have the foreign record name without having + to check external access rights). + """ has_voted = False - vote_ids = self.pool.get('res.users').name_get(cr, uid, [user.id for user in msg.vote_user_ids], context=context) + vote_ids = self.pool.get('res.users').name_get(cr, SUPERUSER_ID, [user.id for user in msg.vote_user_ids], context=context) for vote in vote_ids: if vote[0] == uid: has_voted = True break - attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context)] - author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0] - author_user_id = self.pool.get('res.users').name_get(cr, uid, [msg.author_id.user_ids[0].id], context=context)[0] - partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context) + try: + attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context)] + except (orm.except_orm, osv.except_osv): + attachment_ids = [] + try: + author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0] + is_author = uid in msg.author_id.user_ids + except (orm.except_orm, osv.except_osv): + author_id = False + is_author = False + try: + partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context) + except (orm.except_orm, osv.except_osv): + partner_ids = [] return { 'id': msg.id, 'type': msg.type, @@ -193,7 +210,7 @@ class mail_message(osv.Model): 'subject': msg.subject, 'date': msg.date, 'author_id': author_id, - 'author_user_id': author_user_id, + 'is_author': is_author, 'partner_ids': partner_ids, 'child_ids': [], 'vote_user_ids': vote_ids, @@ -246,7 +263,7 @@ class mail_message(osv.Model): limit = limit or self._message_read_limit context = context or {} if not ids: - ids = self.search(cr, uid, domain, context=context, limit=limit) + ids = self.search(cr, SUPERUSER_ID, domain, context=context, limit=limit) messages = self.browse(cr, uid, ids, context=context) result = [] @@ -294,41 +311,99 @@ class mail_message(osv.Model): cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""") def check_access_rule(self, cr, uid, ids, operation, context=None): - """ mail.message access rule check - - message received (a notification exists) -> ok - - check rules of related document if exists - - fallback on normal mail.message check """ + """ Access rules of mail.message: + - read: if + - notification exist (I receive pushed message) OR + - author_id = pid (I am the author) OR + - I can read the related document if res_model, res_id + - Otherwise: raise + - create: if + - I am in the document message_follower_ids OR + - I can write on the related document if res_model, res_id + - Otherwise: raise + - write: if + - I can write on the related document if res_model, res_id + - Otherwise: raise + - unlink: if + - I can write on the related document if res_model, res_id + - Otherwise: raise + """ + if uid == SUPERUSER_ID: + return if isinstance(ids, (int, long)): ids = [ids] + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0] - # check messages for which you have a notification - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id - not_obj = self.pool.get('mail.notification') - not_ids = not_obj.search(cr, uid, [ - ('partner_id', '=', partner_id), - ('message_id', 'in', ids), - ], context=context) - notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, uid, not_ids, context=context) - if notification.message_id.id in ids] + # Read mail_message.ids to have their values + model_record_ids = {} + message_values = dict.fromkeys(ids) + cr.execute('SELECT DISTINCT id, model, res_id, author_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,)) + for id, rmod, rid, author_id in cr.fetchall(): + message_values[id] = {'res_model': rmod, 'res_id': rid, 'author_id': author_id} + if rmod: + model_record_ids.setdefault(rmod, set()).add(rid) + + # Read: Check for received notifications -> could become an ir.rule, but not till we do not have a many2one variable field + if operation == 'read': + not_obj = self.pool.get('mail.notification') + not_ids = not_obj.search(cr, SUPERUSER_ID, [ + ('partner_id', '=', partner_id), + ('message_id', 'in', ids), + ], context=context) + notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)] + else: + notified_ids = [] + # Read: Check messages you are author -> could become an ir.rule, but not till we do not have a many2one variable field + if operation == 'read': + author_ids = [mid for mid, message in message_values.iteritems() + if message.get('author_id') and message.get('author_id') == partner_id] + else: + author_ids = [] + + # Create: Check message_follower_ids + if operation == 'create': + doc_follower_ids = [] + for model, mids in model_record_ids.items(): + fol_obj = self.pool.get('mail.followers') + fol_ids = fol_obj.search(cr, SUPERUSER_ID, [ + ('res_model', '=', model), + ('res_id', 'in', list(mids)), + ('partner_id', '=', partner_id), + ], context=context) + fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)] + doc_follower_ids += [mid for mid, message in message_values.iteritems() + if message.get('res_model') == model and message.get('res_id') in fol_mids] + else: + doc_follower_ids = [] - # check messages linked to an existing document + # Calculate remaining ids, and related model/res_ids model_record_ids = {} - document_ids = [] - cr.execute('SELECT DISTINCT id, model, res_id FROM mail_message WHERE id = ANY (%s)', (ids,)) - for id, rmod, rid in cr.fetchall(): - if not (rmod and rid): - continue - document_ids.append(id) - model_record_ids.setdefault(rmod, set()).add(rid) + other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids)) + for id in other_ids: + if message_values[id]['res_model']: + model_record_ids.setdefault(message_values[id]['res_model'], set()).add(message_values[id]['res_id']) + + # CRUD: Access rights related to the document + document_related_ids = [] for model, mids in model_record_ids.items(): model_obj = self.pool.get(model) mids = model_obj.exists(cr, uid, mids) - model_obj.check_access_rights(cr, uid, operation) - model_obj.check_access_rule(cr, uid, mids, operation, context=context) - - # fall back on classic operation for other ids - other_ids = set(ids).difference(set(notified_ids), set(document_ids)) - super(mail_message, self).check_access_rule(cr, uid, other_ids, operation, context=None) + if operation in ['create', 'write', 'unlink']: + model_obj.check_access_rights(cr, uid, 'write') + model_obj.check_access_rule(cr, uid, mids, 'write', context=context) + else: + model_obj.check_access_rights(cr, uid, operation) + model_obj.check_access_rule(cr, uid, mids, operation, context=context) + document_related_ids += [mid for mid, message in message_values.iteritems() + if message.get('res_model') == model and message.get('res_id') in mids] + + # Calculate remaining ids: if not void, raise an error + other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids), set(document_related_ids)) + if not other_ids: + return + raise orm.except_orm(_('Access Denied'), + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \ + (self._description, operation)) def create(self, cr, uid, values, context=None): if not values.get('message_id') and values.get('res_id') and values.get('model'): @@ -337,6 +412,13 @@ class mail_message(osv.Model): self.notify(cr, uid, newid, context=context) return newid + def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): + """ Override to explicitely call check_access_rule, that is not called + by the ORM. It instead directly fetches ir.rules and apply them. """ + res = super(mail_message, self).read(cr, uid, ids, fields=fields, context=context, load=load) + self.check_access_rule(cr, uid, ids, 'read', context=context) + return res + def unlink(self, cr, uid, ids, context=None): # cascade-delete attachments that are directly attached to the message (should only happen # for mail.messages that act as parent for a standalone mail.mail record). @@ -360,12 +442,14 @@ class mail_message(osv.Model): partners_to_notify |= set(partner.id for partner in message.partner_ids) # add all followers and set add them in partner_ids if message.model and message.res_id: - record = self.pool.get(message.model).browse(cr, uid, message.res_id, context=context) + record = self.pool.get(message.model).browse(cr, SUPERUSER_ID, message.res_id, context=context) extra_notified = set(partner.id for partner in record.message_follower_ids) missing_notified = extra_notified - partners_to_notify if missing_notified: - message.write({'partner_ids': [(4, p_id) for p_id in missing_notified]}) + self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, p_id) for p_id in missing_notified]}, context=context) partners_to_notify |= extra_notified + # # remove uid from partners + self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(3, uid)]}, context=context) self.pool.get('mail.notification').notify(cr, uid, list(partners_to_notify), newid, context=context) def copy(self, cr, uid, id, default=None, context=None): diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml index 914937a34f3..8e7129e8165 100644 --- a/addons/mail/mail_message_view.xml +++ b/addons/mail/mail_message_view.xml @@ -76,13 +76,6 @@ <field name="view_mode">tree,form</field> <field name="search_view_id" ref="view_message_search"/> </record> - - <act_window domain="[('partner_id', '=', active_id), ('email_from', '!=', False)]" - context="{'default_partner_id': active_id}" - id="act_res_partner_emails" name="Emails" - res_model="mail.message" - src_model="res.partner" - view_id="view_message_tree"/> <!-- Add menu entry in Settings/Email --> <menuitem name="Messages" id="menu_mail_message" parent="base.menu_email" action="action_view_mail_message"/> @@ -95,7 +88,7 @@ </record> <record id="action_mail_my_feeds" model="ir.actions.client"> - <field name="name">My Feeds</field> + <field name="name">My Posts</field> <field name="tag">mail.wall</field> <field name="params" eval=""{'domain': [('author_id.user_ids', 'in', [uid])], 'context': {'default_model': 'res.users', 'default_res_id': uid} }""/> diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index abee20a35f8..c671dd55ec4 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -30,6 +30,7 @@ import xmlrpclib from email.message import Message from mail_message import decode +from openerp import SUPERUSER_ID from osv import osv, fields from tools.safe_eval import safe_eval as eval @@ -118,26 +119,27 @@ class mail_thread(osv.AbstractModel): def _get_message_data(self, cr, uid, ids, name, args, context=None): res = dict((id, dict(message_unread=False, message_summary='')) for id in ids) - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] + # search for unread messages, by reading directly mail.notification, as SUPERUSER notif_obj = self.pool.get('mail.notification') - notif_ids = notif_obj.search(cr, uid, [ + notif_ids = notif_obj.search(cr, SUPERUSER_ID, [ ('partner_id.user_ids', 'in', [uid]), ('message_id.res_id', 'in', ids), ('message_id.model', '=', self._name), ('read', '=', False) ], context=context) - for notif in notif_obj.browse(cr, uid, notif_ids, context=context): + for notif in notif_obj.browse(cr, SUPERUSER_ID, notif_ids, context=context): res[notif.message_id.res_id]['message_unread'] = True - for thread in self.browse(cr, uid, ids, context=context): - cls = res[thread.id]['message_unread'] and ' class="oe_kanban_mail_new"' or '' - res[thread.id]['message_summary'] = "<span%s><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" % (cls, len(thread.message_comment_ids), len(thread.message_follower_ids)) - res[thread.id]['message_is_follower'] = user.partner_id.id in [follower.id for follower in thread.message_follower_ids] + for thread in self.read(cr, uid, ids, ['message_follower_ids', 'message_comment_ids', 'message_ids'], context=context): + cls = res[thread['id']]['message_unread'] and ' class="oe_kanban_mail_new"' or '' + res[thread['id']]['message_summary'] = "<span%s><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" % (cls, len(thread['message_comment_ids']), len(thread['message_follower_ids'])) + res[thread['id']]['message_is_follower'] = partner_id in thread['message_follower_ids'] return res def _search_unread(self, cr, uid, obj=None, name=None, domain=None, context=None): - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] res = {} notif_obj = self.pool.get('mail.notification') notif_ids = notif_obj.search(cr, uid, [ @@ -196,6 +198,13 @@ class mail_thread(osv.AbstractModel): fol_obj.unlink(cr, uid, fol_ids, context=context) return super(mail_thread, self).unlink(cr, uid, ids, context=context) + def copy(self, cr, uid, id, default=None, context=None): + default = default or {} + default['message_ids'] = [] + default['message_comment_ids'] = [] + default['message_follower_ids'] = [] + return super(mail_thread, self).copy(cr, uid, id, default=default, context=context) + #------------------------------------------------------ # mail.message wrappers and tools #------------------------------------------------------ @@ -620,7 +629,7 @@ class mail_thread(osv.AbstractModel): """ Wrapper on message_subscribe, using users. If user_ids is not provided, subscribe uid instead. """ if not user_ids: user_ids = [uid] - partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)] + partner_ids = [user['partner_id'][0] for user in self.pool.get('res.users').read(cr, uid, user_ids, ['partner_id'], context=context)] return self.message_subscribe(cr, uid, ids, partner_ids, context=context) def message_subscribe(self, cr, uid, ids, partner_ids, context=None): @@ -631,7 +640,7 @@ class mail_thread(osv.AbstractModel): """ Wrapper on message_subscribe, using users. If user_ids is not provided, unsubscribe uid instead. """ if not user_ids: user_ids = [uid] - partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)] + partner_ids = [user['partner_id'][0] for user in self.pool.get('res.users').read(cr, uid, user_ids, ['partner_id'], context=context)] return self.message_unsubscribe(cr, uid, ids, partner_ids, context=context) def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None): diff --git a/addons/mail/res_partner_view.xml b/addons/mail/res_partner_view.xml index 2a8430926d5..5c051fc89b3 100644 --- a/addons/mail/res_partner_view.xml +++ b/addons/mail/res_partner_view.xml @@ -9,6 +9,7 @@ <field name="arch" type="xml"> <xpath expr="//sheet" position="after"> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/> <field name="message_follower_ids" widget="mail_followers"/> diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 2ec74086a34..26048e7a40c 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -1,13 +1,14 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_mail_global,mail.mail,model_mail_mail,base.group_system,1,1,1,1
-access_mail_message_all,mail.message.all,model_mail_message,,1,0,0,0
+access_mail_message_all,mail.message.all,model_mail_message,,1,0,1,0
access_mail_message_group_user,mail.message.group.user,model_mail_message,base.group_user,1,1,1,1
-access_mail_thread,mail.thread,model_mail_thread,base.group_user,1,1,1,0
-access_mail_followers_all,mail.followers.all,model_mail_followers,,1,1,1,1
-access_mail_notification_all,mail.notification.all,model_mail_notification,,1,1,1,1
-access_mail_group,mail.group,model_mail_group,base.group_user,1,1,1,1
+access_mail_mail_all,mail.mail.all,model_mail_mail,,0,0,1,0
+access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1
+access_mail_followers_all,mail.followers.all,model_mail_followers,,0,0,0,0
+access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
+access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,0,0
+access_mail_notification_aystem,mail.notification.system,model_mail_notification,base.group_system,1,1,1,1
+access_mail_group_all,mail.group.all,model_mail_group,,1,0,0,0
+access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1
+access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0
-access_mail_alias_system,mail.alias,model_mail_alias,base.group_system,1,1,1,1
-access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
-access_mail_mail_manager,mail.mail,model_mail_mail,group_mail_manager,1,1,1,1
access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1
diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml index 1d6d8d90579..b503565bd11 100644 --- a/addons/mail/security/mail_security.xml +++ b/addons/mail/security/mail_security.xml @@ -2,20 +2,6 @@ <openerp> <data> - <!-- CATEGORY --> - <record model="ir.module.category" id="module_category_social"> - <field name="name">Home</field> - <field name="parent_id" ref="base.module_category_tools"/> - <field name="sequence">26</field> - </record> - - <!-- GROUPS --> - <record id="group_mail_manager" model="res.groups"> - <field name="name">Mail manager</field> - <field name="comment"></field> - <field name="category_id" ref="module_category_social"/> - </record> - <!-- RULES --> <record id="group_rule_public_and_joined" model="ir.rule"> <field name="name">Mail.group: access only public and joined groups</field> @@ -24,5 +10,18 @@ <field name="domain_force">['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.id]), '&', ('public','=','groups'), ('group_public_id','in', [x.id for x in user.groups_id])]</field> </record> + <!-- + This rule can not be uncommented, because we have a more wide method in mail.message. When we implement a many2one_variable field, we will be able to uncomment this. + + <record id="mail_message_read_partner_or_author" model="ir.rule"> + <field name="name">mail.message: read if notified or author</field> + <field name="model_id" ref="model_mail_message"/> + <field name="domain_force">['|', ('partner_ids', 'in', user.partner_id.id), ('author_id', '=', user.partner_id.id)]</field> + <field name="perm_create" eval="False"/> + <field name="perm_write" eval="False"/> + <field name="perm_unlink" eval="False"/> + </record> + --> + </data> </openerp> diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css index 717d0439486..e69eaadbcdb 100644 --- a/addons/mail/static/src/css/mail.css +++ b/addons/mail/static/src/css/mail.css @@ -125,6 +125,14 @@ /* ------------------------------------------------------------ */ +/* Followers +/* ------------------------------------------------------------ */ + +.openerp div.oe_mail_recthread_aside h4 { + display: inline-block; +} + +/* ------------------------------------------------------------ */ /* Thread /* ------------------------------------------------------------ */ @@ -224,7 +232,7 @@ clear: both; } -.openerp .oe_mail_msg_content a { +.openerp .oe_chatter a { cursor: pointer; } @@ -258,30 +266,6 @@ display: none; } -/*--------------------------------------------------------------*/ -/* mail.vote -/*--------------------------------------------------------------*/ - -.openerp .oe_mail_msg_content button.oe_mail_msg_vote { - height:21px; - width: 30px; - padding: 1px; - background: #8A89BA; - margin-top: -4px; -} - -.openerp .oe_mail_msg_content button.oe_mail_msg_vote_true { - background:#DC5F59; -} - -.openerp .oe_mail_msg_content button.oe_mail_msg_vote span { - color: white; -} - -.openerp .oe_mail_msg_content span.oe_mail_vote_count{ - color: #807FB4; -} - /* ------------------------------------------------------------ */ /* mail.compose.message form view & OpenERP hacks diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 57c777441e7..196c54c8296 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -56,11 +56,6 @@ openerp.mail = function(session) { return session.origin + '/web/binary/saveas?session_id=' + session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id=' + attachment['id']; }, - /** Check if the current user is the message author */ - is_author: function (widget, message_user_id) { - return (widget.session && widget.session.uid != 0 && widget.session.uid == message_user_id); - }, - /** Replaces some expressions * - :name - shortcut to an image */ @@ -301,6 +296,7 @@ openerp.mail = function(session) { // TDE TODO: check for deferred, not sure it is correct this._super.apply(this, arguments); this.bind_events(); + this.display_user_avatar(); // fetch and display message, using message_ids if set var display_done = $.when(this.message_fetch(true, [], {})).then(this.proxy('do_customize_display')); // add message composition form view @@ -313,7 +309,6 @@ openerp.mail = function(session) { /** Customize the display * - show_header_compose: show the composition form in the header */ do_customize_display: function() { - this.display_user_avatar(); if (this.options.show_header_compose) { this.$el.find('div.oe_mail_thread_action').eq(0).show(); } @@ -349,7 +344,8 @@ openerp.mail = function(session) { // event: click on 'Reply by email' in msg side menu this.$el.on('click', 'a.oe_mail_msg_reply_by_email', function (event) { if (! self.compose_message_widget) return true; - var msg_id = event.srcElement.dataset.msg_id; + evt = event.target || event.srcElement; + var msg_id = evt.dataset.msg_id; if (! msg_id) return false; self.compose_message_widget.refresh({ 'default_composition_mode': 'reply', @@ -370,9 +366,10 @@ openerp.mail = function(session) { on_message_read: function (event) { //TDE: TODO - var msg_id = event.srcElement.dataset.id; + evt = event.target || event.srcElement; + var msg_id = evt.dataset.id; if (! msg_id) return false; - $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).remove(); + $(evt).parents('li.oe_mail_thread_msg').eq(0).remove(); return this.ds_notification.call('set_message_read', [parseInt(msg_id)]); }, @@ -489,8 +486,7 @@ openerp.mail = function(session) { * - record.date: formatting according to the user timezone * - record.timerelative: relative time givein by timeago lib * - record.avatar: image url - * - record.attachment_ids[].url: url of each attachment - * - record.is_author: is the current user the author of the record */ + * - record.attachment_ids[].url: url of each attachment */ display_record: function (record) { // formatting and additional fields record.date = session.web.format_value(record.date, {type:"datetime"}); @@ -504,7 +500,6 @@ openerp.mail = function(session) { var attach = record.attachment_ids[l]; attach['url'] = mail.ChatterUtils.get_attachment_url(this.session, attach); } - record.is_author = mail.ChatterUtils.is_author(this, record.author_user_id[0]); // add to internal storage this.records[record.id] = record; // render, add the expand feature @@ -647,12 +642,15 @@ openerp.mail = function(session) { // update domain var domain = this.options.domain.concat([['model', '=', this.view.model], ['res_id', '=', this.view.datarecord.id]]); // create and render Thread widget + var show_header_compose = this.view.is_action_enabled('edit') || + (this.getParent().fields.message_is_follower && this.getParent().fields.message_is_follower.get_value()); this.$el.find('div.oe_mail_recthread_main').empty(); var thread = new mail.Thread(self, domain, this.options.context, { 'thread_level': this.options.thread_level, - 'use_composer': true, + 'show_header_compose': show_header_compose, + 'use_composer': show_header_compose, 'show_dd_delete': true, - 'show_reply_by_email': true }); + 'show_reply_by_email': show_header_compose }); return thread.appendTo(this.$el.find('div.oe_mail_recthread_main')); }, }); diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js index d1dd9f89fb7..743c4704330 100644 --- a/addons/mail/static/src/js/mail_followers.js +++ b/addons/mail/static/src/js/mail_followers.js @@ -45,8 +45,8 @@ openerp_mail_followers = function(session, mail) { }, reinit: function() { - this.$el.find('button.oe_mail_button_follow').hide(); - this.$el.find('button.oe_mail_button_unfollow').hide(); + this.message_is_follower == undefined; + this.display_buttons(); }, bind_events: function() { @@ -55,7 +55,7 @@ openerp_mail_followers = function(session, mail) { .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); }) .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); this.$el.on('click', 'button.oe_mail_button_follow', function () { self.do_follow(); }); - this.$el.on('click', 'button.oe_mail_button_invite', function(event) { + this.$el.on('click', 'a.oe_mail_invite', function(event) { action = { type: 'ir.actions.act_window', res_model: 'mail.wizard.invite', @@ -74,41 +74,65 @@ openerp_mail_followers = function(session, mail) { read_value: function() { var self = this; - return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).pipe(function (results) { - return results[0].message_follower_ids; - }).pipe(this.proxy('set_value')); + return this.ds_model.read_ids([this.view.datarecord.id], ['message_is_follower', 'message_follower_ids']).then(function (results) { + self.set_value(results[0].message_follower_ids, results[0].message_is_follower); + }); }, - set_value: function(value_) { + set_value: function(value_, message_is_follower) { this.reinit(); if (! this.view.datarecord.id || session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) { - this.$el.find('div.oe_mail_recthread_aside').hide(); + this.$('div.oe_mail_recthread_aside').hide(); return; } - return this.fetch_followers(value_ || this.get_value()); + return this.fetch_followers(value_ || this.get_value(), message_is_follower); + }, + + fetch_followers: function (value_, message_is_follower) { + this.value = value_; + this.message_is_follower = message_is_follower || (this.getParent().fields.message_is_follower && this.getParent().fields.message_is_follower.get_value()); + return this.ds_follow.call('read', [value_, ['name', 'user_ids']]).pipe(this.proxy('display_followers'), this.proxy('display_generic')); }, - fetch_followers: function (value_) { - return this.ds_follow.call('read', [value_, ['name', 'user_ids']]).pipe(this.proxy('display_followers')); + + /* Display generic info about follower, for people not having access to res_partner */ + display_generic: function (error, event) { + event.preventDefault(); + var node_user_list = this.$('ul.oe_mail_followers_display').empty(); + // format content: Followers (You and 0 other) // Followers (3) + var content = this.options.title; + if (this.message_is_follower) { + content += ' (You and ' + (this.value.length-1) + ' other)'; + } + else { + content += ' (' + this.value.length + ')' + } + this.$('div.oe_mail_recthread_followers h4').html(content); + this.display_buttons(); + return $.when(); }, /** Display the followers, evaluate is_follower directly */ display_followers: function (records) { var self = this; - this.message_is_follower = _.indexOf(_.flatten(_.pluck(records, 'user_ids')), this.session.uid) != -1; - var node_user_list = this.$el.find('ul.oe_mail_followers_display').empty(); - this.$el.find('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')'); + var node_user_list = this.$('ul.oe_mail_followers_display').empty(); + this.$('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')'); _(records).each(function (record) { record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id); $(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list); }); - if (this.message_is_follower) { - this.$el.find('button.oe_mail_button_follow').hide(); - this.$el.find('button.oe_mail_button_unfollow').show(); } - else { - this.$el.find('button.oe_mail_button_follow').show(); - this.$el.find('button.oe_mail_button_unfollow').hide(); } + this.display_buttons(); + }, + + display_buttons: function () { + this.$('button.oe_mail_button_follow').hide(); + this.$('button.oe_mail_button_unfollow').hide(); + this.$('span.oe_mail_invite_wrapper').hide(); + if (! this.view.is_action_enabled('edit')) return; + this.$('span.oe_mail_invite_wrapper').show(); + if (this.message_is_follower) { this.$('button.oe_mail_button_unfollow').show(); } + else if (this.message_is_follower == false) { this.$('button.oe_mail_button_follow').show(); } }, do_follow: function () { diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index e3586e723e4..c54bd4dbb2d 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -140,7 +140,7 @@ <li t-if="options.show_record_name and record.record_name and record.subject and options.thread_level > 0"> <a t-attf-href="#model=#{record.model}&id=#{record.res_id}"><t t-raw="record.record_name"/></a> </li> - <li><a t-attf-href="#model=res.partner&id=#{record.author_id[0]}"><t t-raw="record.author_id[1]"/></a></li> + <li t-if="record.author_id"><a t-attf-href="#model=res.partner&id=#{record.author_id[0]}"><t t-raw="record.author_id[1]"/></a></li> <li><span t-att-title="record.date"><t t-raw="record.timerelative"/></span></li> <t t-call="mail.thread.message.vote"/> <li t-if="options.show_reply"><a class="oe_mail_msg_reply">Reply</a></li> @@ -185,7 +185,7 @@ <t t-if='record.vote_user_ids.length > 0'> <span class="oe_left oe_mail_vote_count"><t t-esc="record.vote_user_ids.length"/> votes</span> </t> - <button t-attf-class="oe_mail_msg_vote oe_mail_msg_vote_#{record.has_voted}" t-attf-data-msg_id="{record.id}"> + <button t-attf-class="oe_mail_msg_vote oe_tag" t-attf-data-msg_id="{record.id}"> <t t-if="! record.has_voted"><span>+1</span></t> <t t-if="record.has_voted"><span>-1</span></t> </button> diff --git a/addons/mail/static/src/xml/mail_followers.xml b/addons/mail/static/src/xml/mail_followers.xml index cf31ab3e24c..203a7bce789 100644 --- a/addons/mail/static/src/xml/mail_followers.xml +++ b/addons/mail/static/src/xml/mail_followers.xml @@ -7,7 +7,6 @@ --> <div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override"> <div class="oe_mail_recthread_actions"> - <button type="button" class="oe_mail_button_invite">Invite</button> <button type="button" class="oe_mail_button_follow">Follow</button> <button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button> </div> @@ -18,7 +17,7 @@ </div> <div class="oe_mail_recthread_followers"> <t t-if="widget.options.title"> - <h4><t t-raw="widget.options.title"/></h4> + <h4><t t-raw="widget.options.title"/></h4><span class="oe_mail_invite_wrapper"> · <a class="oe_mail_invite" >Invite partners</a></span> </t> <ul class="oe_mail_followers_display"></ul> </div> diff --git a/addons/mail/tests/__init__.py b/addons/mail/tests/__init__.py index d63c5634cc3..2d48fa233c4 100644 --- a/addons/mail/tests/__init__.py +++ b/addons/mail/tests/__init__.py @@ -18,10 +18,11 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################## -from . import test_mail +from . import test_mail, test_mail_access_rights checks = [ test_mail, + test_mail_access_rights, ] -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/mail/tests/test_mail_access_rights.py b/addons/mail/tests/test_mail_access_rights.py new file mode 100644 index 00000000000..104d7f28ffe --- /dev/null +++ b/addons/mail/tests/test_mail_access_rights.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2012-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 +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################## + +from openerp.addons.mail.tests import test_mail +from osv.orm import except_orm + + +class test_mail_access_rights(test_mail.TestMailMockups): + + def setUp(self): + super(test_mail_access_rights, self).setUp() + cr, uid = self.cr, self.uid + self.mail_group = self.registry('mail.group') + self.mail_message = self.registry('mail.message') + self.mail_notification = self.registry('mail.notification') + self.res_users = self.registry('res.users') + self.res_groups = self.registry('res.groups') + self.res_partner = self.registry('res.partner') + + # create a 'pigs' group that will be used through the various tests + self.group_pigs_id = self.mail_group.create(self.cr, self.uid, + {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'}) + + # Find Employee group + group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user') + self.group_employee_id = group_employee_ref and group_employee_ref[1] or False + + # Create Bert (without groups) and Raoul( employee) + self.user_bert_id = self.res_users.create(cr, uid, {'name': 'Bert Tartopoils', 'login': 'bert', 'groups_id': [(6, 0, [])]}) + self.user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]}) + self.user_bert = self.res_users.browse(cr, uid, self.user_bert_id) + self.partner_bert_id = self.user_bert.partner_id.id + self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id) + self.partner_raoul_id = self.user_raoul.partner_id.id + + def test_00_mail_message_read_access_rights(self): + """ Test basic mail_message read access rights. """ + cr, uid = self.cr, self.uid + partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id + user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id + + # Prepare groups: Pigs (employee), Jobs (public) + self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message') + self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'}) + + # ---------------------------------------- + # CASE1: Bert, basic mail.message read access + # ---------------------------------------- + + # Do: create a new mail.message + message_id = self.mail_message.create(cr, uid, {'body': 'My Body'}) + # Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc + self.assertRaises(except_orm, self.mail_message.read, + cr, user_bert_id, message_id) + # Do: message is pushed to Bert + notif_id = self.mail_notification.create(cr, uid, {'message_id': message_id, 'partner_id': partner_bert_id}) + # Test: Bert reads the message, ok because notification pushed + self.mail_message.read(cr, user_bert_id, message_id) + # Do: remove notification + self.mail_notification.unlink(cr, uid, notif_id) + # Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc + self.assertRaises(except_orm, self.mail_message.read, + cr, self.user_bert_id, message_id) + # Do: Bert is now the author + self.mail_message.write(cr, uid, [message_id], {'author_id': partner_bert_id}) + # Test: Bert reads the message, ok because Bert is the author + self.mail_message.read(cr, user_bert_id, message_id) + # Do: Bert is not the author anymore + self.mail_message.write(cr, uid, [message_id], {'author_id': partner_raoul_id}) + # Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc + self.assertRaises(except_orm, self.mail_message.read, + cr, user_bert_id, message_id) + # Do: message is attached to a document Bert can read, Jobs + self.mail_message.write(cr, uid, [message_id], {'model': 'mail.group', 'res_id': self.group_jobs_id}) + # Test: Bert reads the message, ok because linked to a doc he is allowed to read + self.mail_message.read(cr, user_bert_id, message_id) + # Do: message is attached to a document Bert cannot read, Pigs + self.mail_message.write(cr, uid, [message_id], {'model': 'mail.group', 'res_id': self.group_pigs_id}) + # Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc + self.assertRaises(except_orm, self.mail_message.read, + cr, user_bert_id, message_id) + + def test_05_mail_message_search_access_rights(self): + """ Test mail_message search override about access rights. """ + self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True') + + def test_10_mail_flow_access_rights(self): + """ Test a Chatter-looks alike flow. """ + cr, uid = self.cr, self.uid + partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id + user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id + + # Prepare groups: Pigs (employee), Jobs (public) + self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message') + self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'}) + + # ---------------------------------------- + # CASE1: Bert, without groups + # ---------------------------------------- + # Do: Bert creates a group, should crash because perm_create only for employees + self.assertRaises(except_orm, + self.mail_group.create, + cr, user_bert_id, {'name': 'Bert\'s Group'}) + # Do: Bert reads Jobs basic fields, ok because public = read access on the group + self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['name', 'description']) + # Do: Bert browse Pigs, ok (no direct browse of partners) + self.mail_group.browse(cr, user_bert_id, self.group_jobs_id) + # Do: Bert reads Jobs messages, ok because read access on the group => read access on its messages + jobs_message_ids = self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['message_ids'])['message_ids'] + self.mail_message.read(cr, user_bert_id, jobs_message_ids) + # Do: Bert reads Jobs followers, ko because partner are accessible to employees or partner manager + jobs_followers_ids = self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['message_follower_ids'])['message_follower_ids'] + self.assertRaises(except_orm, + self.res_partner.read, + cr, user_bert_id, jobs_followers_ids) + # Do: Bert comments Jobs, ko because no write access on the group and not in the followers + self.assertRaises(except_orm, + self.mail_group.message_post, + cr, user_bert_id, self.group_jobs_id, body='I love Pigs') + # Do: add Bert to jobs followers + self.mail_group.message_subscribe(cr, uid, [self.group_jobs_id], [partner_bert_id]) + # Do: Bert comments Jobs, ok because he is now in the followers + self.mail_group.message_post(cr, user_bert_id, self.group_jobs_id, body='I love Pigs') + + # Do: Bert reads Pigs, should crash because mail.group security=groups only for employee group + self.assertRaises(except_orm, + self.mail_group.read, + cr, user_bert_id, self.group_pigs_id) + + # ---------------------------------------- + # CASE1: Raoul, employee + # ---------------------------------------- + # Do: Bert read Pigs, ok because public + self.mail_group.read(cr, user_raoul_id, self.group_pigs_id) + # Do: Bert read Jobs, ok because group_public_id = employee + self.mail_group.read(cr, user_raoul_id, self.group_jobs_id) diff --git a/addons/marketing_campaign/res_partner.py b/addons/marketing_campaign/res_partner.py index 98ac53b7b29..8ae1ab8645a 100644 --- a/addons/marketing_campaign/res_partner.py +++ b/addons/marketing_campaign/res_partner.py @@ -29,7 +29,13 @@ class res_partner(osv.osv): 'partner_id', 'Workitems', readonly=True), } - + + def copy(self, cr, uid, id, default={}, context=None): + default.update({ + 'workitem_ids': [], + }) + return super(res_partner, self).copy(cr, uid, id, default=default, context=context) + res_partner() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/marketing_campaign/res_partner_view.xml b/addons/marketing_campaign/res_partner_view.xml index 5b959625086..63c65ccd97f 100644 --- a/addons/marketing_campaign/res_partner_view.xml +++ b/addons/marketing_campaign/res_partner_view.xml @@ -12,7 +12,7 @@ <attribute name="invisible">False</attribute> </xpath> <xpath expr="//page[@name='page_history']" position="inside"> - <group name="grp_campaign" string="Campaign"> + <group name="grp_campaign" string="Campaigns"> <field name="workitem_ids" colspan="4" nolabel="1"/> </group> </xpath> diff --git a/addons/membership/membership.py b/addons/membership/membership.py index 2a006814629..87af723f2f9 100644 --- a/addons/membership/membership.py +++ b/addons/membership/membership.py @@ -338,7 +338,7 @@ class Partner(osv.osv): -Paid Member: A member who has paid the membership amount."""), 'membership_start': fields.function( _membership_date, multi = 'membeship_start', - string = 'Start Membership Date', type = 'date', + string = 'Membership Start Date', type = 'date', store = { 'account.invoice': (_get_invoice_partner, ['state'], 10), 'membership.membership_line': (_get_partner_id, ['state'], 10, ), @@ -346,7 +346,7 @@ class Partner(osv.osv): }, help="Date from which membership becomes active."), 'membership_stop': fields.function( _membership_date, - string = 'Stop Membership Date', type='date', multi='membership_stop', + string = 'Membership End Date', type='date', multi='membership_stop', store = { 'account.invoice': (_get_invoice_partner, ['state'], 10), 'membership.membership_line': (_get_partner_id, ['state'], 10), diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 049c7c712fb..00a5790b67e 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -106,6 +106,7 @@ class mrp_routing_workcenter(osv.osv): """ _name = 'mrp.routing.workcenter' _description = 'Work Center Usage' + _order = 'sequence' _columns = { 'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True), 'name': fields.char('Name', size=64, required=True), @@ -226,6 +227,7 @@ class mrp_bom(osv.osv): 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', context=c), } _order = "sequence" + _parent_name = "bom_id" _sql_constraints = [ ('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \ 'You should install the mrp_subproduct module if you want to manage extra products on BoMs !'), @@ -671,7 +673,7 @@ class mrp_production(osv.osv): for (production_id,name) in self.name_get(cr, uid, ids): production = self.browse(cr, uid, production_id) - if production.move_prod_id: + if production.move_prod_id and production.move_prod_id.location_id.id != production.location_dest_id.id: move_obj.write(cr, uid, [production.move_prod_id.id], {'location_id': production.location_dest_id.id}) self.action_ready_send_note(cr, uid, [production_id], context) @@ -829,7 +831,8 @@ class mrp_production(osv.osv): 'ref': wc.code, 'product_id': wc.product_id.id, 'unit_amount': wc_line.hour, - 'product_uom_id': wc.product_id.uom_id.id + 'product_uom_id': wc.product_id.id and wc.product_id.uom_id.id or False + } ) if wc.costs_journal_id and wc.costs_general_account_id: value = wc_line.cycle * wc.costs_cycle @@ -845,7 +848,8 @@ class mrp_production(osv.osv): 'ref': wc.code, 'product_id': wc.product_id.id, 'unit_amount': wc_line.cycle, - 'product_uom_id': wc.product_id.uom_id.id + 'product_uom_id': wc.product_id.id and wc.product_id.uom_id.id or False + } ) return amount diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml index c003711ceb1..a198d736bbc 100644 --- a/addons/mrp/mrp_view.xml +++ b/addons/mrp/mrp_view.xml @@ -409,8 +409,9 @@ </page> </notebook> <div class="oe_chatter"> - <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> - <field name="message_follower_ids" widget="mail_followers"/> + <field name="message_is_follower" invisible="1"/> + <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> + <field name="message_follower_ids" widget="mail_followers"/> </div> </form> </field> @@ -544,9 +545,7 @@ <field name="name">Bill of Materials Structure</field> <field name="type">ir.actions.act_window</field> <field name="res_model">mrp.bom</field> - <field name="domain">[('id','in',active_ids)]</field> - <field name="view_type">tree</field> - <field name="view_mode">tree</field> + <field name="domain">[('bom_id', '=',active_ids)]</field> <field name="view_id" ref="mrp_bom_tree_view"/> </record> <record id="ir_BOM_structure" model="ir.values"> @@ -675,7 +674,7 @@ <field name="product_id"/> <field name="product_qty" string="Quantity"/> <field name="product_uom" string="Unit of Measure" groups="product.group_uom"/> - <field name="prodlot_id" groups="stock.group_production_lot"/> + <field name="prodlot_id" groups="stock.group_production_lot" context="{'product_id': product_id}"/> <field name="state" invisible="1"/> <button name="%(stock.move_consume)d" string="Consume Products" type="action" @@ -799,6 +798,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/mrp_operations/mrp_operations_view.xml b/addons/mrp_operations/mrp_operations_view.xml index 834f3000472..0bcce6063f1 100644 --- a/addons/mrp_operations/mrp_operations_view.xml +++ b/addons/mrp_operations/mrp_operations_view.xml @@ -107,6 +107,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/mrp_repair/mrp_repair_view.xml b/addons/mrp_repair/mrp_repair_view.xml index 55cbe49c75b..976961693c6 100644 --- a/addons/mrp_repair/mrp_repair_view.xml +++ b/addons/mrp_repair/mrp_repair_view.xml @@ -189,6 +189,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/note/note_view.xml b/addons/note/note_view.xml index 62b007aab6e..d5af65f8763 100644 --- a/addons/note/note_view.xml +++ b/addons/note/note_view.xml @@ -117,8 +117,11 @@ <field name="stage_id" domain="[('user_id','=',uid)]" widget="statusbar" clickable="1"/> </header> <field name="memo" widget="html"/><!-- editor_width="100%%" editor_height="60%%" --> - <field name="message_follower_ids" widget="mail_followers"/> - <field class="oe_chatter" name="message_ids" widget="mail_thread"/> + <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> + <field name="message_ids" widget="mail_thread"/> + <field name="message_follower_ids" widget="mail_followers"/> + </div> </form> </field> </record> diff --git a/addons/portal/portal_data.xml b/addons/portal/portal_data.xml index a980552a854..90a570bc1d5 100644 --- a/addons/portal/portal_data.xml +++ b/addons/portal/portal_data.xml @@ -5,6 +5,7 @@ <!-- Mail group for the company's news --> <record id="company_news_feed" model="mail.group"> <field name="name">Company's news</field> + <field name="public">public</field> </record> <record id="action_news" model="ir.actions.act_window"> @@ -17,6 +18,7 @@ <!-- Mail group for the company's jobs --> <record id="company_jobs" model="mail.group"> <field name="name">Company's jobs</field> + <field name="public">public</field> </record> <record id="action_jobs" model="ir.actions.act_window"> diff --git a/addons/portal/portal_demo.xml b/addons/portal/portal_demo.xml index a8727b99556..e8d0fb4159b 100644 --- a/addons/portal/portal_demo.xml +++ b/addons/portal/portal_demo.xml @@ -9,7 +9,9 @@ <field name="password">portal</field> <!-- Avoid auto-including this user in any default group --> <field name="groups_id" eval="[(5,)]"/> - <field name="partner_id" eval="ref('base.res_partner_1')"/> + <field name="supplier" eval="False"/> + <field name="customer" eval="True"/> + <field name="email">demo@portal.wrong.address</field> </record> <!-- Add the demo user to the portal (and therefore to the portal member group) --> diff --git a/addons/portal/security/ir.model.access.csv b/addons/portal/security/ir.model.access.csv index 2d7a17e0016..1c4ee5964bd 100644 --- a/addons/portal/security/ir.model.access.csv +++ b/addons/portal/security/ir.model.access.csv @@ -1,8 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_mail_message,mail.message,mail.model_mail_message,group_portal,1,0,1,1 -access_mail_message_all,mail.message.all,mail.model_mail_message,group_portal,1,0,0,0 -access_mail_thread,mail.thread,mail.model_mail_thread,group_portal,1,0,0,0 -access_mail_followers,mail.followers,mail.model_mail_followers,group_portal,1,0,1,1 -access_mail_notification,mail.notification,mail.model_mail_notification,group_portal,1,0,1,0 -access_mail_group,mail.group,mail.model_mail_group,group_portal,1,0,0,0 -access_mail_alias,mail.alias,mail.model_mail_alias,group_portal,1,0,0,0 +access_mail_message_portal,mail.message.portal,mail.model_mail_message,group_portal,1,0,1,1 diff --git a/addons/portal/tests/test_portal.py b/addons/portal/tests/test_portal.py index 5e992fa06ea..653a05d40aa 100644 --- a/addons/portal/tests/test_portal.py +++ b/addons/portal/tests/test_portal.py @@ -21,15 +21,18 @@ from openerp.addons.mail.tests import test_mail from openerp.tools import append_content_to_html +from osv.orm import except_orm class test_portal(test_mail.TestMailMockups): def setUp(self): super(test_portal, self).setUp() + cr, uid = self.cr, self.uid self.ir_model = self.registry('ir.model') self.mail_group = self.registry('mail.group') self.mail_mail = self.registry('mail.mail') + self.mail_message = self.registry('mail.message') self.res_users = self.registry('res.users') self.res_partner = self.registry('res.partner') @@ -37,7 +40,42 @@ class test_portal(test_mail.TestMailMockups): self.group_pigs_id = self.mail_group.create(self.cr, self.uid, {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'}) - def test_00_mail_invite(self): + # Find Portal group + group_portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal') + self.group_portal_id = group_portal_ref and group_portal_ref[1] or False + + # Create Chell (portal user) + self.user_chell_id = self.res_users.create(cr, uid, {'name': 'Chell Gladys', 'login': 'chell', 'groups_id': [(6, 0, [self.group_portal_id])]}) + self.user_chell = self.res_users.browse(cr, uid, self.user_chell_id) + self.partner_chell_id = self.user_chell.partner_id.id + + def test_00_access_rights(self): + """ Test basic mail_message and mail_group access rights for portal users. """ + cr, uid = self.cr, self.uid + partner_chell_id = self.partner_chell_id + user_chell_id = self.user_chell_id + + # Prepare group: Pigs (portal) + self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message') + self.mail_group.write(cr, uid, [self.group_pigs_id], {'name': 'Jobs', 'public': 'groups', 'group_public_id': self.group_portal_id}) + + # ---------------------------------------- + # CASE1: Chell will use the Chatter + # ---------------------------------------- + + # Do: Chell reads Pigs messages, ok because restricted to portal group + message_ids = self.mail_group.read(cr, user_chell_id, self.group_pigs_id, ['message_ids'])['message_ids'] + self.mail_message.read(cr, user_chell_id, message_ids) + # Do: Chell posts a message on Pigs, crash because can not write on group or is not in the followers + self.assertRaises(except_orm, + self.mail_group.message_post, + cr, user_chell_id, self.group_pigs_id, body='Message') + # Do: Chell is added to Pigs followers + self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [partner_chell_id]) + # Test: Chell posts a message on Pigs, ok because in the followers + self.mail_group.message_post(cr, user_chell_id, self.group_pigs_id, body='Message') + + def test_50_mail_invite(self): cr, uid = self.cr, self.uid user_admin = self.res_users.browse(cr, uid, uid) self.mail_invite = self.registry('mail.wizard.invite') diff --git a/addons/portal_claim/security/portal_security.xml b/addons/portal_claim/security/portal_security.xml index 9fc47c4cfb0..a88c8ce7835 100644 --- a/addons/portal_claim/security/portal_security.xml +++ b/addons/portal_claim/security/portal_security.xml @@ -5,7 +5,7 @@ <record id="portal_stock_picking_user_rule" model="ir.rule"> <field name="name">Portal Personal Claims</field> <field ref="crm_claim.model_crm_claim" name="model_id"/> - <field name="domain_force">[('partner_id','child_of',user.partner_id.id)]</field> + <field name="domain_force">[('message_follower_ids','in',user.partner_id.id)]</field> <field name="groups" eval="[(4, ref('portal.group_portal'))]"/> </record> diff --git a/addons/portal_crm/wizard/contact.py b/addons/portal_crm/wizard/contact.py index 036993e1720..b671ced2275 100644 --- a/addons/portal_crm/wizard/contact.py +++ b/addons/portal_crm/wizard/contact.py @@ -40,32 +40,51 @@ class crm_contact_us(osv.TransientModel): r = self.pool.get('res.company').search(cr, uid, [], context=context) return r - def _get_current_user_email(self, cr, uid, context=None): + def _get_user_name(self, cr, uid, context=None): """ - If the user is logged in (i.e. not anonymous), get the user's email to - pre-fill the email_from field. + If the user is logged in (i.e. not anonymous), get the user's name to + pre-fill the partner_name field. + Same goes for the other _get_user_attr methods. - @return current user's email if the user isn't "anonymous", None otherwise + @return current user's name if the user isn't "anonymous", None otherwise """ - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + user = self.pool.get('res.users').read(cr, uid, uid, ['login'], context) - if (user.login != 'anonymous'): - return user.email + if (user['login'] != 'anonymous'): + return self.pool.get('res.users').name_get(cr, uid, uid, context)[0][1] + else: + return None + + def _get_user_email(self, cr, uid, context=None): + user = self.pool.get('res.users').read(cr, uid, uid, ['login', 'email'], context) + + if (user['login'] != 'anonymous' and user['email']): + return user['email'] + else: + return None + + def _get_user_phone(self, cr, uid, context=None): + user = self.pool.get('res.users').read(cr, uid, uid, ['login', 'phone'], context) + + if (user['login'] != 'anonymous' and user['phone']): + return user['phone'] else: return None _defaults = { - 'email_from' : _get_current_user_email, - 'company_ids' : _get_companies, + 'partner_name': _get_user_name, + 'email_from': _get_user_email, + 'phone': _get_user_phone, + 'company_ids': _get_companies, } def create(self, cr, uid, values, context=None): """ - Since they potentially sensitive, we don't want any user to be able to - read datas generated through this module. That's why we'll write those - information in the crm.lead table and leave blank entries in the - portal_crm.crm_contact_us table. This is why the create() method is - overwritten. + Since they are potentially sensitive, we don't want any user to be able + to read datas generated through this module. That's why we'll write + those information directly in the crm.lead table and leave blank + entries in the portal_crm.crm_contact_us table. + This is why the create() method is overwritten. """ crm_lead = self.pool.get('crm.lead') @@ -88,11 +107,20 @@ class crm_contact_us(osv.TransientModel): def submit(self, cr, uid, ids, context=None): """ When the form is submitted, redirect the user to a "Thanks" message """ - return {'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'view_type': 'form', - 'res_model': self._name, - 'res_id': ids[0], - 'view_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal_crm', 'wizard_contact_form_view_thanks')[1], - 'target': 'inline' - } + return { + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'view_type': 'form', + 'res_model': self._name, + 'res_id': ids[0], + 'view_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal_crm', 'wizard_contact_form_view_thanks')[1], + 'target': 'inline', + } + + def _needaction_domain_get(self, cr, uid, context=None): + """ + This model doesn't need the needactions mechanism inherited from + crm_lead, so simply override the method to return an empty domain + and, therefore, 0 needactions. + """ + return False diff --git a/addons/portal_event/security/ir.model.access.csv b/addons/portal_event/security/ir.model.access.csv index 7418b08bb4b..fc652f4bcd1 100644 --- a/addons/portal_event/security/ir.model.access.csv +++ b/addons/portal_event/security/ir.model.access.csv @@ -1,3 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_event,event,event.model_event_event,portal.group_portal,1,0,0,0
-access_registration,registration,event.model_event_registration,portal.group_portal,1,0,0,0
+access_registration,registration,event.model_event_registration,portal.group_portal,1,1,1,1
diff --git a/addons/portal_event/security/portal_security.xml b/addons/portal_event/security/portal_security.xml index 74b2372c147..8f17808345b 100644 --- a/addons/portal_event/security/portal_security.xml +++ b/addons/portal_event/security/portal_security.xml @@ -12,7 +12,7 @@ <record id="portal_registration_rule" model="ir.rule"> <field name="name">Portal Personal Registrations</field> <field ref="event.model_event_registration" name="model_id"/> - <field name="domain_force">[('partner_id','child_of',user.partner_id.id)]</field> + <field name="domain_force">[('user_id','=',user.id)]</field> <field name="groups" eval="[(4, ref('portal.group_portal'))]"/> </record> diff --git a/addons/portal_project/security/portal_security.xml b/addons/portal_project/security/portal_security.xml index b9d37cec099..316fb1b4a15 100644 --- a/addons/portal_project/security/portal_security.xml +++ b/addons/portal_project/security/portal_security.xml @@ -5,7 +5,7 @@ <record id="portal_task_rule" model="ir.rule"> <field name="name">Portal Personal Task</field> <field ref="project.model_project_task" name="model_id"/> - <field name="domain_force">[('partner_id','child_of',user.partner_id.id)]</field> + <field name="domain_force">[('message_follower_ids','in',user.partner_id.id)]</field> <field name="groups" eval="[(4, ref('portal.group_portal'))]"/> </record> diff --git a/addons/portal_project_issue/security/portal_security.xml b/addons/portal_project_issue/security/portal_security.xml index 358b5e6a0ad..d58e160051b 100644 --- a/addons/portal_project_issue/security/portal_security.xml +++ b/addons/portal_project_issue/security/portal_security.xml @@ -5,7 +5,7 @@ <record id="portal_issue_rule" model="ir.rule"> <field name="name">Portal Personal Issues</field> <field ref="project_issue.model_project_issue" name="model_id"/> - <field name="domain_force">[('partner_id','child_of',user.partner_id.id)]</field> + <field name="domain_force">[('message_follower_ids','in',user.partner_id.id)]</field> <field name="groups" eval="[(4, ref('portal.group_portal'))]"/> </record> diff --git a/addons/portal_sale/security/portal_security.xml b/addons/portal_sale/security/portal_security.xml index 2f161a73281..d27317d0305 100644 --- a/addons/portal_sale/security/portal_security.xml +++ b/addons/portal_sale/security/portal_security.xml @@ -5,43 +5,43 @@ <!-- Sale Portal Access Rules --> <record id="portal_sale_order_user_rule" model="ir.rule"> <field name="name">Portal Personal Quotations/Sales Orders</field> - <field ref="sale.model_sale_order" name="model_id"/> - <field name="domain_force">[('partner_id','child_of',user.partner_id.id)]</field> + <field name="model_id" ref="sale.model_sale_order"/> + <field name="domain_force">[('message_follower_ids','in',user.partner_id.id)]</field> <field name="groups" eval="[(4, ref('portal.group_portal'))]"/> </record> <record id="portal_stock_picking_user_rule" model="ir.rule"> <field name="name">Portal Personal Delivery Orders</field> <field name="model_id" ref="stock.model_stock_picking"/> - <field name="domain_force">[('partner_id','child_of',[user.partner_id.id])]</field> + <field name="domain_force">[('message_follower_ids','in',user.partner_id.id)]</field> <field name="groups" eval="[(4, ref('portal.group_portal'))]"/> </record> <record id="portal_stock_picking_user_rule" model="ir.rule"> <field name="name">Portal Personal Delivery Orders Out</field> - <field ref="stock.model_stock_picking_out" name="model_id"/> - <field name="domain_force">[('partner_id','child_of',[user.partner_id.id])]</field> + <field name="model_id" ref="stock.model_stock_picking_out"/> + <field name="domain_force">[('message_follower_ids','in',user.partner_id.id)]</field> <field name="groups" eval="[(4, ref('portal.group_portal'))]"/> </record> <record id="portal_account_invoice_user_rule" model="ir.rule"> <field name="name">Portal Personal Account Invoices</field> - <field ref="account.model_account_invoice" name="model_id"/> - <field name="domain_force">[('partner_id','child_of',user.partner_id.id)]</field> + <field name="model_id" ref="account.model_account_invoice"/> + <field name="domain_force">[('message_follower_ids','in',user.partner_id.id)]</field> <field name="groups" eval="[(4, ref('portal.group_portal'))]"/> </record> <record id="portal_personal_payment" model="ir.rule"> <field name="name">Portal Personal Payments</field> - <field ref="account_voucher.model_account_voucher" name="model_id"/> - <field name="domain_force">[('partner_id','child_of',user.partner_id.id)]</field> + <field name="model_id" ref="account_voucher.model_account_voucher"/> + <field name="domain_force">[('message_follower_ids','in',user.partner_id.id)]</field> <field name="groups" eval="[(4, ref('portal.group_portal'))]"/> </record> <record id="portal_personal_contact" model="ir.rule"> <field name="name">Portal Personal Contacts</field> - <field ref="base.model_res_partner" name="model_id"/> - <field name="domain_force">[('id','child_of',user.partner_id.id)]</field> + <field name="model_id" ref="base.model_res_partner"/> + <field name="domain_force">[('message_follower_ids','in',user.partner_id.id)]</field> <field name="groups" eval="[(4, ref('portal.group_portal'))]"/> </record> diff --git a/addons/procurement/company.py b/addons/procurement/company.py index d544181a6e8..9d064c0e649 100644 --- a/addons/procurement/company.py +++ b/addons/procurement/company.py @@ -30,9 +30,9 @@ class company(osv.osv): "today and today+range are skipped for future computation."), } _defaults = { - 'schedule_range': 80.0, + 'schedule_range': 730.0, } company() -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/procurement/procurement.py b/addons/procurement/procurement.py index 842f9ec27fa..c9b09761ed0 100644 --- a/addons/procurement/procurement.py +++ b/addons/procurement/procurement.py @@ -97,9 +97,9 @@ class procurement_order(osv.osv): 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True), 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True), 'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'), - 'close_move': fields.boolean('Close Move at end', required=True), + 'close_move': fields.boolean('Close Move at end'), 'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True), - 'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]}, + 'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]}, readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \ " a make to order method."), @@ -333,8 +333,8 @@ class procurement_order(osv.osv): move_obj = self.pool.get('stock.move') for procurement in self.browse(cr, uid, ids, context=context): if procurement.product_qty <= 0.00: - raise osv.except_osv(_('Insufficient Data!'), - _('Quantity in procurement order(s) must be greater than 0.')) + raise osv.except_osv(_('Data Insufficient !'), + _('Please check the quantity in procurement order(s) for the product "%s", it should not be 0 or less!' % procurement.product_id.name)) if procurement.product_id.type in ('product', 'consu'): if not procurement.move_id: source = procurement.location_id.id @@ -423,24 +423,26 @@ class procurement_order(osv.osv): """ return 0 + # XXX action_cancel() should accept a context argument def action_cancel(self, cr, uid, ids): - """ Cancels procurement and writes move state to Assigned. + """Cancel Procurements and either cancel or assign the related Stock Moves, depending on the procurement configuration. + @return: True """ - todo = [] - todo2 = [] + to_assign = [] + to_cancel = [] move_obj = self.pool.get('stock.move') for proc in self.browse(cr, uid, ids): if proc.close_move and proc.move_id: if proc.move_id.state not in ('done', 'cancel'): - todo2.append(proc.move_id.id) + to_cancel.append(proc.move_id.id) else: if proc.move_id and proc.move_id.state == 'waiting': - todo.append(proc.move_id.id) - if len(todo2): - move_obj.action_cancel(cr, uid, todo2) - if len(todo): - move_obj.write(cr, uid, todo, {'state': 'assigned'}) + to_assign.append(proc.move_id.id) + if len(to_cancel): + move_obj.action_cancel(cr, uid, to_cancel) + if len(to_assign): + move_obj.write(cr, uid, to_assign, {'state': 'assigned'}) self.write(cr, uid, ids, {'state': 'cancel'}) self.cancel_send_note(cr, uid, ids, context=None) wf_service = netsvc.LocalService("workflow") @@ -548,6 +550,19 @@ class stock_warehouse_orderpoint(osv.osv): result[orderpoint.id] = procurement_ids return result + def _check_product_uom(self, cr, uid, ids, context=None): + ''' + Check if the UoM has the same category as the product standard UoM + ''' + if not context: + context = {} + + for rule in self.browse(cr, uid, ids, context=context): + if rule.product_id.uom_id.category_id.id != rule.product_uom.category_id.id: + return False + + return True + _columns = { 'name': fields.char('Name', size=32, required=True), 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the orderpoint without removing it."), @@ -580,6 +595,9 @@ class stock_warehouse_orderpoint(osv.osv): _sql_constraints = [ ('qty_multiple_check', 'CHECK( qty_multiple > 0 )', 'Qty Multiple must be greater than zero.'), ] + _constraints = [ + (_check_product_uom, 'You have to select a product unit of measure in the same category than the default unit of measure of the product', ['product_id', 'product_uom']), + ] def default_get(self, cr, uid, fields, context=None): res = super(stock_warehouse_orderpoint, self).default_get(cr, uid, fields, context) @@ -610,9 +628,10 @@ class stock_warehouse_orderpoint(osv.osv): """ if product_id: prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context) + d = {'product_uom': [('category_id', '=', prod.uom_id.category_id.id)]} v = {'product_uom': prod.uom_id.id} - return {'value': v} - return {} + return {'value': v, 'domain': d} + return {'domain': {'product_uom': []}} def copy(self, cr, uid, id, default=None, context=None): if not default: diff --git a/addons/procurement/procurement_view.xml b/addons/procurement/procurement_view.xml index bf73b383216..34715f19e11 100644 --- a/addons/procurement/procurement_view.xml +++ b/addons/procurement/procurement_view.xml @@ -103,6 +103,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/product/pricelist.py b/addons/product/pricelist.py index f189b854fe5..3bc6e6d6814 100644 --- a/addons/product/pricelist.py +++ b/addons/product/pricelist.py @@ -203,7 +203,7 @@ class product_pricelist(osv.osv): if partner: partner_where = 'base <> -2 OR %s IN (SELECT name FROM product_supplierinfo WHERE product_id = %s) ' - partner_args = (partner, product_id) + partner_args = (partner, tmpl_id) else: partner_where = 'base <> -2 ' partner_args = () diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 8c19aee026f..8ffa4732c13 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -203,6 +203,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/product_visible_discount/__openerp__.py b/addons/product_visible_discount/__openerp__.py index 09cf5ce61fd..81b99b8ac0a 100644 --- a/addons/product_visible_discount/__openerp__.py +++ b/addons/product_visible_discount/__openerp__.py @@ -35,7 +35,7 @@ To this end, a new check box named 'Visible Discount' is added to the pricelist If the check box is unchecked, we will have on Sale Order and Invoice lines: Unit price=225, Discount=0,00, Net price=225. """, - 'depends': ['sale'], + 'depends': ["sale","purchase"], 'demo': [], 'data': ['product_visible_discount_view.xml'], 'auto_install': False, diff --git a/addons/project/i18n/nl.po b/addons/project/i18n/nl.po index f543f48834e..f8c8bc84ba9 100644 --- a/addons/project/i18n/nl.po +++ b/addons/project/i18n/nl.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 01:37+0100\n" -"PO-Revision-Date: 2012-06-28 12:50+0000\n" +"PO-Revision-Date: 2012-09-29 14:57+0000\n" "Last-Translator: Erwin <Unknown>\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-09-21 04:42+0000\n" -"X-Generator: Launchpad (build 15985)\n" +"X-Launchpad-Export-Date: 2012-09-30 04:44+0000\n" +"X-Generator: Launchpad (build 16049)\n" #. module: project #: view:report.project.task.user:0 @@ -1326,7 +1326,7 @@ msgstr "" #. module: project #: view:project.task:0 msgid "Delegation" -msgstr "" +msgstr "Delegeren" #. module: project #: model:ir.model,name:project.model_res_users diff --git a/addons/project/project.py b/addons/project/project.py index dbb579e2f7d..0d92d71a45f 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -509,6 +509,8 @@ def Project(): model_name=vals.get('alias_model', 'project.task'), context=context) vals['alias_id'] = alias_id + if vals.get('partner_id', False): + vals['type'] = 'contract' project_id = super(project, self).create(cr, uid, vals, context) mail_alias.write(cr, uid, [vals['alias_id']], {'alias_defaults': {'project_id': project_id} }, context) self.create_send_note(cr, uid, [project_id], context=context) @@ -690,7 +692,6 @@ class task(base_stage, osv.osv): if not default.get('remaining_hours', False): default['remaining_hours'] = float(self.read(cr, uid, id, ['planned_hours'])['planned_hours']) default['active'] = True - default['stage_id'] = False if not default.get('name', False): default['name'] = self.browse(cr, uid, id, context=context).name or '' if not context.get('copy',False): @@ -768,7 +769,7 @@ class task(base_stage, osv.osv): }), 'user_id': fields.many2one('res.users', 'Assigned to'), 'delegated_user_id': fields.related('child_ids', 'user_id', type='many2one', relation='res.users', string='Delegated To'), - 'partner_id': fields.many2one('res.partner', 'Contact'), + 'partner_id': fields.many2one('res.partner', 'Customer'), 'work_ids': fields.one2many('project.task.work', 'task_id', 'Work done'), 'manager_id': fields.related('project_id', 'analytic_account_id', 'user_id', type='many2one', relation='res.users', string='Project Manager'), 'company_id': fields.many2one('res.company', 'Company'), diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 4ce02f5a437..e302199a65c 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -92,12 +92,12 @@ </div> <group> <group> + <field name="privacy_visibility"/> <field name="user_id" string="Project Manager" attrs="{'readonly':[('state','in',['close', 'cancelled'])]}"/> - <field name="partner_id" string="Contact" on_change="onchange_partner_id(partner_id)"/> </group> <group> - <field name="privacy_visibility"/> - <field name="analytic_account_id" readonly="1" required="0"/> + <field name="partner_id" on_change="onchange_partner_id(partner_id)"/> + <field name="analytic_account_id" readonly="1" required="0" attrs="{'invisible': [('analytic_account_id','=',False)]}"/> </group> </group> <notebook> @@ -121,14 +121,14 @@ </field> </page> <page string="Other Info"> - <group string="Email Interface" colspan="2"> - <div attrs="{'invisible': [('alias_domain', '=', False)]}"> - <field name="alias_id" invisible="1" required="0"/> - <label for="alias_name" class="oe_edit_only "/> - <field name="alias_name" class="oe_inline" attrs="{'required': [('alias_id', '!=', False)]}"/>@<field name="alias_domain" class="oe_inline"/> - </div> - </group> <group> + <group string="Email Interface" attrs="{'invisible': [('alias_domain', '=', False)]}"> + <div> + <field name="alias_id" invisible="1" required="0"/> + <label for="alias_name" class="oe_edit_only "/> + <field name="alias_name" class="oe_inline" attrs="{'required': [('alias_id', '!=', False)]}"/>@<field name="alias_domain" class="oe_inline"/> + </div> + </group> <group string="Administration" groups="project.group_time_work_estimation_tasks"> <field name="planned_hours" widget="float_time"/> <field name="effective_hours" widget="float_time"/> @@ -136,8 +136,7 @@ </group> <group string="Miscellaneous" name="misc"> <field name="date"/> - <field name="parent_id" domain="[('id','!=',analytic_account_id)]" context="{'current_model': 'project.project'}"/> - <field name="priority"/> + <field name="priority" groups="base.group_no_one"/> <field name="active" attrs="{'invisible':[('state','in',['open', 'pending', 'template'])]}"/> <field name="currency_id" groups="base.group_multi_currency" required="1"/> @@ -151,6 +150,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers" help="Follow this project to automatically follow all related tasks and issues."/> </div> @@ -240,7 +240,7 @@ <a t-if="record.use_tasks.raw_value" name="%(act_project_project_2_project_task_all)d" type="action"> <field name="task_count"/> Tasks</a> </div> <div class="oe_kanban_footer_left"> - <span> + <span groups="project.group_time_work_estimation_tasks"> <span class="oe_e">R</span> <t t-esc="Math.round(record.effective_hours.raw_value)"/>/<t t-esc="Math.round(record.planned_hours.raw_value)"/> <field name="company_uom_id"/> </span> @@ -468,6 +468,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -519,7 +520,7 @@ <div class="oe_kanban_content"> <div><b><field name="name"/></b></div> <div> - <field name="project_id"/> + <field name="project_id"/><br/> <t t-esc="kanban_text_ellipsis(record.description.value, 160)"/><br/> <t t-if="record.date_deadline.raw_value and record.date_deadline.raw_value lt (new Date())" t-set="red">oe_kanban_text_red</t> <span t-attf-class="#{red || ''}"><i><field name="date_deadline"/></i></span> @@ -713,7 +714,7 @@ </record> <record id="ir_project_task_open" model="ir.values"> - <field eval=" 'tree_but_open'" name="key2"/> + <field eval="'tree_but_open'" name="key2"/> <field eval="'project.project'" name="model"/> <field name="name">View project's tasks</field> <field eval="'ir.actions.act_window,'+str(dblc_proj)" name="value"/> @@ -757,7 +758,7 @@ <field name="model">project.task.type</field> <field name="arch" type="xml"> <tree string="Task Stage"> - <field name="sequence"/> + <field name="sequence" widget="handle"/> <field name="name"/> <field name="state"/> </tree> diff --git a/addons/project_issue/project_issue_demo.xml b/addons/project_issue/project_issue_demo.xml index 0a4eee3067b..9759d15dabb 100644 --- a/addons/project_issue/project_issue_demo.xml +++ b/addons/project_issue/project_issue_demo.xml @@ -31,7 +31,7 @@ </record> <record id="crm_case_programnotgivingproperoutput0" model="project.issue"> - <field eval="time.strftime('%Y-%m-15 12:50:00')" name="date"/> + <field eval="time.strftime('%Y-%m-15 12:50:00')" name="date"/> <field name="priority">3</field> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_1"/> diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 3214b93c8d3..e41495590d7 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -161,6 +161,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/project_long_term/project_long_term.py b/addons/project_long_term/project_long_term.py index 5b88d5209c8..b19238200e5 100644 --- a/addons/project_long_term/project_long_term.py +++ b/addons/project_long_term/project_long_term.py @@ -292,7 +292,7 @@ account_analytic_account() class project_task(osv.osv): _inherit = "project.task" _columns = { - 'phase_id': fields.many2one('project.phase', 'Project Phase'), + 'phase_id': fields.many2one('project.phase', 'Project Phase', domain="[('project_id', '=', project_id)]"), } project_task() diff --git a/addons/project_long_term/project_long_term_view.xml b/addons/project_long_term/project_long_term_view.xml index 909f838a0f8..69fe335c1b0 100644 --- a/addons/project_long_term/project_long_term_view.xml +++ b/addons/project_long_term/project_long_term_view.xml @@ -102,7 +102,7 @@ <label for="use_phases"/> </xpath> <xpath expr="//div[@name='buttons']" position='inside'> - <button icon="terp-gtk-go-back-rtl" name="%(act_project_phases)d" + <button name="%(act_project_phases)d" string="Phases" type="action" attrs="{'invisible':[('use_phases','=', 0)]}"/> </xpath> diff --git a/addons/project_mrp/project_procurement.py b/addons/project_mrp/project_procurement.py index f9f6fb6a279..1c347c8ae02 100644 --- a/addons/project_mrp/project_procurement.py +++ b/addons/project_mrp/project_procurement.py @@ -37,8 +37,10 @@ class procurement_order(osv.osv): """ Checks if task is done or not. @return: True or False. """ - return all(proc.product_id.type != 'service' or (proc.task_id and proc.task_id.state in ('done', 'cancelled')) \ - for proc in self.browse(cr, uid, ids, context=context)) + for p in self.browse(cr, uid, ids, context=context): + if (p.product_id.type=='service') and (p.procure_method=='make_to_order') and p.task_id and (p.task_id.state not in ('done', 'cancelled')): + return False + return True def check_produce_service(self, cr, uid, procurement, context=None): return True @@ -81,7 +83,7 @@ class procurement_order(osv.osv): 'project_id': project and project.id or False, 'company_id': procurement.company_id.id, },context=context) - self.write(cr, uid, [procurement.id], {'task_id': task_id, 'state': 'running'}, context=context) + self.write(cr, uid, [procurement.id], {'task_id': task_id, 'state': 'running', 'message':'from project: task created.'}, context=context) self.running_send_note(cr, uid, ids, context=None) return task_id diff --git a/addons/project_timesheet/project_timesheet.py b/addons/project_timesheet/project_timesheet.py index 1e50848201c..488c780b988 100644 --- a/addons/project_timesheet/project_timesheet.py +++ b/addons/project_timesheet/project_timesheet.py @@ -46,23 +46,27 @@ class project_project(osv.osv): def open_timesheets(self, cr, uid, ids, context=None): """ open Timesheets view """ project = self.browse(cr, uid, ids[0], context) - try: - journal_id = self.pool.get('ir.model.data').get_object(cr, uid, 'hr_timesheet', 'analytic_journal').id - except ValueError: - journal_id = False view_context = { 'search_default_account_id': [project.analytic_account_id.id], 'default_account_id': project.analytic_account_id.id, - 'default_journal_id': journal_id, } + help = _("""<p class="oe_view_nocontent_create">Record your timesheets for the project '%s'.</p>""") + try: + if project.to_invoice and project.partner_id: + help+= _("""<p>Timesheets on this project may be invoiced to %s, according to the terms defined in the contract.</p>""" ) % (project.partner_id.name,) + except: + # if the user do not have access rights on the partner + pass + return { 'type': 'ir.actions.act_window', - 'name': _('Bill Tasks Works'), - 'res_model': 'account.analytic.line', + 'name': _('Timesheets'), + 'res_model': 'hr.analytic.timesheet', 'view_type': 'form', 'view_mode': 'tree,form', 'context': view_context, 'nodestroy': True, + 'help': help } project_project() diff --git a/addons/purchase/partner.py b/addons/purchase/partner.py index 2edcf5ec0d8..6649ebfea9d 100644 --- a/addons/purchase/partner.py +++ b/addons/purchase/partner.py @@ -26,9 +26,13 @@ class res_partner(osv.osv): _inherit = 'res.partner' def _purchase_order_count(self, cr, uid, ids, field_name, arg, context=None): - res = {} - for partner in self.browse(cr, uid, ids, context=context): - res[partner.id] = len(partner.purchase_order_ids) + res = dict(map(lambda x: (x,0), ids)) + # this user may not have access to user rights + try: + for partner in self.browse(cr, uid, ids, context=context): + res[partner.id] = len(partner.purchase_order_ids) + except: + pass return res def copy(self, cr, uid, id, default=None, context=None): diff --git a/addons/purchase/partner_view.xml b/addons/purchase/partner_view.xml index da1f841ba9e..7b03861759a 100644 --- a/addons/purchase/partner_view.xml +++ b/addons/purchase/partner_view.xml @@ -20,12 +20,14 @@ <field name="view_mode">tree,form,graph</field> <field name="context">{'search_default_partner_id': active_id}</field> <field name="groups_id" eval="[(4, ref('purchase.group_purchase_user'))]"/> - <field name="help"> - This supplier has no RFQ or Purchase Order. Click here to create a new RFQ. - <p> - The "Quotation" is the first step of the Purchases flow. Manage your purchases from quotation to invoice. - <p> - You will be able to buy products (manage receptions) as well as services (create projects). + <field name="help" type="html"> + <p class="oe_view_nocontent_create"> + This supplier has no purchase order. Click to create a new RfQ. + </p><p> + The request for quotation is the first step of the purchases flow. Once + converted into a purchase order, you will be able to control the reception + of the products and the supplier invoice. + </p> </field> </record> @@ -53,12 +55,15 @@ <field name="view_mode">tree,form,graph</field> <field name="domain">[('type','=','in_invoice')]</field> <field name="context">{'search_default_partner_id': active_id, 'default_type': 'in_invoice', 'type': 'in_invoice', 'journal_type': 'purchase'}</field> - <field name="help"> - Click here to create Supplier invoice. - <p> - You can control the invoice from your supplier according to what you purchased or received. - <p> - OpenERP can also generate draft invoices automatically from purchase orders or receipts. + <field name="help" type="html"> + <p class="oe_view_nocontent_create"> + Click here to record a supplier invoice. + </p><p> + Supplier invoices can be pre-generated based on purchase + orders or receptions. This allows you to control invoices + you receive from your supplier according to the draft + document in OpenERP. + </p> </field> </record> diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index d2060016866..8502fd7ee3f 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -792,14 +792,6 @@ class purchase_order_line(osv.osv): res[line.id] = cur_obj.round(cr, uid, cur, taxes['total']) return res - def _get_uom_id(self, cr, uid, context=None): - try: - proxy = self.pool.get('ir.model.data') - result = proxy.get_object_reference(cr, uid, 'product', 'product_uom_unit') - return result[1] - except Exception, ex: - return False - _columns = { 'name': fields.text('Description', required=True), 'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True), @@ -826,7 +818,6 @@ class purchase_order_line(osv.osv): } _defaults = { - 'product_uom' : _get_uom_id, 'product_qty': lambda *a: 1.0, 'state': lambda *args: 'draft', 'invoiced': lambda *a: 0, @@ -1057,9 +1048,10 @@ class procurement_order(osv.osv): purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context) #Passing partner_id to context for purchase order line integrity of Line name - context.update({'lang': partner.lang, 'partner_id': partner_id}) + new_context = context.copy() + new_context.update({'lang': partner.lang, 'partner_id': partner_id}) - product = prod_obj.browse(cr, uid, procurement.product_id.id, context=context) + product = prod_obj.browse(cr, uid, procurement.product_id.id, context=new_context) taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids) @@ -1088,7 +1080,7 @@ class procurement_order(osv.osv): 'company_id': procurement.company_id.id, 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False } - res[procurement.id] = self.create_procurement_purchase_order(cr, uid, procurement, po_vals, line_vals, context=context) + res[procurement.id] = self.create_procurement_purchase_order(cr, uid, procurement, po_vals, line_vals, context=new_context) self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': res[procurement.id]}) self.running_send_note(cr, uid, [procurement.id], context=context) return res diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index 7702d182b09..23bb8e7e278 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -285,6 +285,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> diff --git a/addons/purchase/report/request_quotation.rml b/addons/purchase/report/request_quotation.rml index dc222a42398..66fd58060b8 100644 --- a/addons/purchase/report/request_quotation.rml +++ b/addons/purchase/report/request_quotation.rml @@ -167,4 +167,4 @@ <para style="terp_default_9">[[ user.signature or '' ]]</para> </pto> </story> -</document> +</document>
\ No newline at end of file diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index c17d145fea1..5ab19390656 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -267,16 +267,13 @@ class procurement_order(osv.osv): } def make_po(self, cr, uid, ids, context=None): res = {} - sequence_obj = self.pool.get('ir.sequence') requisition_obj = self.pool.get('purchase.requisition') warehouse_obj = self.pool.get('stock.warehouse') procurement = self.browse(cr, uid, ids, context=context)[0] if procurement.product_id.purchase_requisition: - seq_name = sequence_obj.get(cr, uid, 'purchase.order.requisition') warehouse_id = warehouse_obj.search(cr, uid, [('company_id', '=', procurement.company_id.id or company.id)], context=context) res[procurement.id] = requisition_obj.create(cr, uid, { - 'name': seq_name, 'origin': procurement.origin, 'date_end': procurement.date_planned, 'warehouse_id':warehouse_id and warehouse_id[0] or False, diff --git a/addons/purchase_requisition/purchase_requisition_view.xml b/addons/purchase_requisition/purchase_requisition_view.xml index 1fafec92bc7..eab920113a8 100644 --- a/addons/purchase_requisition/purchase_requisition_view.xml +++ b/addons/purchase_requisition/purchase_requisition_view.xml @@ -102,8 +102,9 @@ </notebook> </sheet> <div class="oe_chatter"> - <field name="message_ids" widget="mail_thread"/> - <field name="message_follower_ids" widget="mail_followers"/> + <field name="message_is_follower" invisible="1"/> + <field name="message_ids" widget="mail_thread"/> + <field name="message_follower_ids" widget="mail_followers"/> </div> </form> </field> diff --git a/addons/report_webkit/webkit_report.py b/addons/report_webkit/webkit_report.py index e1ce5a38c6b..b03289c8748 100644 --- a/addons/report_webkit/webkit_report.py +++ b/addons/report_webkit/webkit_report.py @@ -107,7 +107,7 @@ class WebKitParser(report_sxw): tmp_dir = tempfile.gettempdir() out_filename = tempfile.mktemp(suffix=".pdf", prefix="webkit.tmp.") files = [] - file_to_del = [] + file_to_del = [out_filename] if comm_path: command = [comm_path] else: @@ -160,22 +160,33 @@ class WebKitParser(report_sxw): file_to_del.append(html_file.name) command.append(html_file.name) command.append(out_filename) + stderr_fd, stderr_path = tempfile.mkstemp(text=True) + file_to_del.append(stderr_path) try: - status = subprocess.call(command, stderr=subprocess.PIPE) # ignore stderr + status = subprocess.call(command, stderr=stderr_fd) + os.close(stderr_fd) # ensure flush before reading + stderr_fd = None # avoid closing again in finally block + fobj = open(stderr_path, 'r') + error_message = fobj.read() + fobj.close() + if not error_message: + error_message = _('No diagnosis message was provided') + else: + error_message = _('The following diagnosis message was provided:\n') + error_message if status : - raise except_osv( - _('Webkit raise an error!' ), - status - ) - except Exception: - for f_to_del in file_to_del : - os.unlink(f_to_del) - - pdf = file(out_filename, 'rb').read() - for f_to_del in file_to_del : - os.unlink(f_to_del) - - os.unlink(out_filename) + raise except_osv(_('Webkit error' ), + _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message)) + pdf_file = open(out_filename, 'rb') + pdf = pdf_file.read() + pdf_file.close() + finally: + if stderr_fd is not None: + os.close(stderr_fd) + for f_to_del in file_to_del: + try: + os.unlink(f_to_del) + except (OSError, IOError), exc: + _logger.error('cannot remove file %s: %s', f_to_del, exc) return pdf def translate_call(self, src): diff --git a/addons/report_webkit_sample/report/report_webkit_html.mako b/addons/report_webkit_sample/report/report_webkit_html.mako index 164a697f386..3ffca9f669c 100644 --- a/addons/report_webkit_sample/report/report_webkit_html.mako +++ b/addons/report_webkit_sample/report/report_webkit_html.mako @@ -8,7 +8,7 @@ %for inv in objects : <% setLang(inv.partner_id.lang) %> <table class="dest_address"> - <tr><td ><b>${inv.partner_id.title or ''|entity} ${inv.partner_id.name |entity}</b></td></tr> + <tr><td ><b>${inv.partner_id.title and inv.partner_id.title.name or ''|entity} ${inv.partner_id.name |entity}</b></td></tr> <tr><td>${inv.address_invoice_id.street or ''|entity}</td></tr> <tr><td>${inv.address_invoice_id.street2 or ''|entity}</td></tr> <tr><td>${inv.address_invoice_id.zip or ''|entity} ${inv.address_invoice_id.city or ''|entity}</td></tr> diff --git a/addons/sale/report/sale_order.rml b/addons/sale/report/sale_order.rml index 9f7ba06db95..32926cc0626 100644 --- a/addons/sale/report/sale_order.rml +++ b/addons/sale/report/sale_order.rml @@ -153,6 +153,12 @@ </pto_header> <para style="terp_default_8">[[repeatIn(objects,'o')]]</para> <para style="terp_default_8">[[ setLang(o.partner_id.lang) ]]</para> + <para style="terp_default_8"> + <font color="white"> </font> + </para> + <para style="terp_default_8"> + <font color="white"> </font> + </para> <blockTable colWidths="265.0,51.0,225.0" style="Table1"> <tr> <td> diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index 3048b1414cd..0ae2e4f7133 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -93,6 +93,7 @@ class sale_report(osv.osv): left join product_template t on (p.product_tmpl_id=t.id) left join product_uom u on (u.id=l.product_uom) left join product_uom u2 on (u2.id=t.uom_id) + where l.product_id is not null group by l.product_id, l.product_uom_qty, diff --git a/addons/sale/res_config_view.xml b/addons/sale/res_config_view.xml index 9ebbc36f8de..6150d4ce1a3 100644 --- a/addons/sale/res_config_view.xml +++ b/addons/sale/res_config_view.xml @@ -12,10 +12,6 @@ <group> <label for="id" string="Invoicing Process"/> <div> - <div> - <field name="group_invoice_so_lines" on_change="onchange_invoice_methods(group_invoice_so_lines, group_invoice_deli_orders)" class="oe_inline"/> - <label for="group_invoice_so_lines"/> - </div> <div name="timesheet"> <field name="timesheet" class="oe_inline" on_change="onchange_timesheet(timesheet)"/> <label for="timesheet"/> diff --git a/addons/sale/res_partner.py b/addons/sale/res_partner.py index d7bcd5804ee..5e8d05b62c1 100644 --- a/addons/sale/res_partner.py +++ b/addons/sale/res_partner.py @@ -26,9 +26,13 @@ class res_partner(osv.osv): _inherit = 'res.partner' def _sale_order_count(self, cr, uid, ids, field_name, arg, context=None): - res = {} - for partner in self.browse(cr, uid, ids, context): - res[partner.id] = len(partner.sale_order_ids) + res = dict(map(lambda x: (x,0), ids)) + # The current user may not have access rights for sale orders + try: + for partner in self.browse(cr, uid, ids, context): + res[partner.id] = len(partner.sale_order_ids) + except: + pass return res def copy(self, cr, uid, record_id, default=None, context=None): diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index 23d2c556f40..343f06c2d69 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -174,7 +174,7 @@ </h1> <group> <group> - <field name="partner_id" on_change="onchange_partner_id(partner_id)" domain="[('customer','=',True)]" context="{'search_default_customer':1, 'show_address': 1}" options='{"always_reload": true}'/> + <field name="partner_id" on_change="onchange_partner_id(partner_id)" domain="[('customer','=',True)]" context="{'search_default_customer':1}"/> <field name="partner_invoice_id" groups="sale.group_delivery_invoice_address"/> <field name="partner_shipping_id" groups="sale.group_delivery_invoice_address"/> <field name="project_id" context="{'partner_id':partner_id, 'pricelist_id':pricelist_id, 'default_name':name}" groups="sale.group_analytic_accounting" domain="[('type','in',['view','normal','contract'])]"/> @@ -310,6 +310,7 @@ </notebook> </sheet> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -364,7 +365,7 @@ <field name="view_type">form</field> <field name="view_mode">tree,form,calendar,graph</field> <field name="search_view_id" ref="view_sales_order_filter"/> - <field name="context">{}</field> + <field name="context">{'show_address': 1}</field> <field name="domain">[('state','not in',('draft','sent','cancel'))]</field> <field name="help" type="html"> <p class="oe_view_nocontent_create"> @@ -406,7 +407,7 @@ <field name="res_model">sale.order</field> <field name="view_type">form</field> <field name="view_mode">tree,form,calendar,graph</field> - <field name="context">{}</field> + <field name="context">{'show_address': 1}</field> <field name="domain">[('state','in',('draft','sent','cancel'))]</field> <field name="search_view_id" ref="view_sales_order_filter"/> <field name="help" type="html"> diff --git a/addons/sale/security/ir.model.access.csv b/addons/sale/security/ir.model.access.csv index d0d14fc76a1..670c863a022 100644 --- a/addons/sale/security/ir.model.access.csv +++ b/addons/sale/security/ir.model.access.csv @@ -37,7 +37,7 @@ access_product_ul_sale_manager,product.ul salemanager,product.model_product_ul,b access_product_category_sale_manager,product.category salemanager,product.model_product_category,base.group_sale_manager,1,1,1,1
access_product_supplierinfo_user,product.supplierinfo.user,product.model_product_supplierinfo,base.group_sale_salesman,1,0,0,0
access_product_supplierinfo_sale_manager,product.supplierinfo salemanager,product.model_product_supplierinfo,base.group_sale_manager,1,1,1,1
-access_pricelist_partnerinfo_sale_manager,pricelist.partnerinfo salemanager,product.model_pricelist_partnerinfo,base.group_sale_manager,1,1,1,1
+access_pricelist_partnerinfo_sale_manager,pricelist.partnerinfo salemanager,product.model_pricelist_partnerinfo,base.group_sale_manager,1,1,1,1 access_product_price_type_sale_manager,product.price.type salemanager,product.model_product_price_type,base.group_sale_manager,1,1,1,1
access_product_pricelist_type_sale_manager,product.pricelist.type salemanager,product.model_product_pricelist_type,base.group_sale_manager,1,1,1,1
access_product_pricelist_sale_manager,product.pricelist salemanager,product.model_product_pricelist,base.group_sale_manager,1,1,1,1
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index a48b766f3bb..490af81ed41 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -253,7 +253,6 @@ class sale_order(osv.osv): assert mode in ('finished', 'canceled'), _("invalid mode for test_state") finished = True canceled = False - notcanceled = False write_done_ids = [] write_cancel_ids = [] for order in self.browse(cr, uid, ids, context={}): @@ -268,8 +267,6 @@ class sale_order(osv.osv): canceled = True if line.state != 'exception': write_cancel_ids.append(line.id) - else: - notcanceled = True if write_done_ids: self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'}) if write_cancel_ids: @@ -279,9 +276,6 @@ class sale_order(osv.osv): return finished elif mode == 'canceled': return canceled - if notcanceled: - return False - return canceled def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, context=None): return { diff --git a/addons/share/ir_model.py b/addons/share/ir_model.py index 008dd424fa5..0d90aec3eb7 100644 --- a/addons/share/ir_model.py +++ b/addons/share/ir_model.py @@ -40,6 +40,7 @@ class ir_model_access(osv.Model): LEFT JOIN ir_module_category c ON (c.id=g.category_id) WHERE m.model=%s AND + a.active IS true AND (g.share IS NULL or g.share IS false) AND a.perm_''' + access_mode, (model_name,)) return [('%s/%s' % x) if x[0] else x[1] for x in cr.fetchall()] diff --git a/addons/stock/report/report_stock_move.py b/addons/stock/report/report_stock_move.py index aeb34d4265e..4f8aedcd5a6 100644 --- a/addons/stock/report/report_stock_move.py +++ b/addons/stock/report/report_stock_move.py @@ -167,6 +167,7 @@ class report_stock_inventory(osv.osv): help='When the stock move is created it is in the \'Draft\' state.\n After that it is set to \'Confirmed\' state.\n If stock is available state is set to \'Avaiable\'.\n When the picking it done the state is \'Done\'.\ \nThe state is \'Waiting\' if the move is waiting for another one.'), 'location_type': fields.selection([('supplier', 'Supplier Location'), ('view', 'View'), ('internal', 'Internal Location'), ('customer', 'Customer Location'), ('inventory', 'Inventory'), ('procurement', 'Procurement'), ('production', 'Production'), ('transit', 'Transit Location for Inter-Companies Transfers')], 'Location Type', required=True), + 'scrap_location': fields.boolean('scrap'), } def init(self, cr): tools.drop_view_if_exists(cr, 'report_stock_inventory') @@ -177,7 +178,7 @@ CREATE OR REPLACE view report_stock_inventory AS ( to_char(m.date, 'YYYY') as year, to_char(m.date, 'MM') as month, m.partner_id as partner_id, m.location_id as location_id, - m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type, + m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type, l.scrap_location as scrap_location, m.company_id, m.state as state, m.prodlot_id as prodlot_id, @@ -194,14 +195,14 @@ CREATE OR REPLACE view report_stock_inventory AS ( LEFT JOIN stock_location l ON (m.location_id=l.id) GROUP BY m.id, m.product_id, m.product_uom, pt.categ_id, m.partner_id, m.location_id, m.location_dest_id, - m.prodlot_id, m.date, m.state, l.usage, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM') + m.prodlot_id, m.date, m.state, l.usage, l.scrap_location, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM') ) UNION ALL ( SELECT -m.id as id, m.date as date, to_char(m.date, 'YYYY') as year, to_char(m.date, 'MM') as month, m.partner_id as partner_id, m.location_dest_id as location_id, - m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type, + m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type, l.scrap_location as scrap_location, m.company_id, m.state as state, m.prodlot_id as prodlot_id, coalesce(sum(pt.standard_price * m.product_qty * pu.factor / pu2.factor)::decimal, 0.0) as value, @@ -217,7 +218,7 @@ CREATE OR REPLACE view report_stock_inventory AS ( LEFT JOIN stock_location l ON (m.location_dest_id=l.id) GROUP BY m.id, m.product_id, m.product_uom, pt.categ_id, m.partner_id, m.location_id, m.location_dest_id, - m.prodlot_id, m.date, m.state, l.usage, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM') + m.prodlot_id, m.date, m.state, l.usage, l.scrap_location, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM') ) ); """) diff --git a/addons/stock/report/report_stock_move_view.xml b/addons/stock/report/report_stock_move_view.xml index 4c911dd6e7c..9b53ffd6d58 100644 --- a/addons/stock/report/report_stock_move_view.xml +++ b/addons/stock/report/report_stock_move_view.xml @@ -155,7 +155,7 @@ <field name="state"/> <field name="location_type"/> <field name="date"/> - <filter icon="terp-go-home" name="location_type_scrap" string="Scrap" domain="[('location_type','=','scrap')]"/> + <filter icon="terp-go-home" name="location_type_scrap" string="Scrap" domain="[('scrap_location','=','True')]"/> </group> <group expand="1" string="Group By..." > <filter string="Partner" name="group_partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/> diff --git a/addons/stock/res_config_view.xml b/addons/stock/res_config_view.xml index f3abb9bbc95..9cc8553f4ea 100644 --- a/addons/stock/res_config_view.xml +++ b/addons/stock/res_config_view.xml @@ -2,6 +2,16 @@ <openerp> <data> + <!-- After installation of the module, open the related menu --> + <record id="action_client_warehouse_menu" model="ir.actions.client"> + <field name="name">Open Warehouse Menu</field> + <field name="tag">reload</field> + <field name="params" eval="{'menu_id': ref('menu_stock_root')}"/> + </record> + <record id="base.open_menu" model="ir.actions.todo"> + <field name="action_id" ref="action_client_warehouse_menu"/> + <field name="state">open</field> + </record> <record id="view_stock_config_settings" model="ir.ui.view"> <field name="name">stock settings</field> <field name="model">stock.config.settings</field> diff --git a/addons/stock/security/ir.model.access.csv b/addons/stock/security/ir.model.access.csv index b0ae333a9ef..84b475adf1d 100644 --- a/addons/stock/security/ir.model.access.csv +++ b/addons/stock/security/ir.model.access.csv @@ -36,8 +36,8 @@ access_stock_report_tracklots,stock.report.tracklots,model_stock_report_tracklot access_report_stock_move_user,report.stock.move user,model_report_stock_move,stock.group_stock_user,1,0,0,0
access_report_stock_move_manager,report.stock.move manager,model_report_stock_move,stock.group_stock_manager,1,1,1,1
access_report_stock_inventory_user,report.stock.inventory user,model_report_stock_inventory,stock.group_stock_user,1,1,1,1
-access_product_product_stock_user,product_product_stock_user,product.model_product_product,stock.group_stock_user,1,0,0,0
-access_product_template_stock_user,product.template stock user,product.model_product_template,stock.group_stock_user,1,0,0,0
+access_product_product_stock_user,product_product_stock_user,product.model_product_product,stock.group_stock_user,1,1,0,0
+access_product_template_stock_user,product.template stock user,product.model_product_template,stock.group_stock_user,1,1,0,0
access_account_invoice_user,account.invoice stock user,account.model_account_invoice,stock.group_stock_user,1,1,1,0
access_account_invoice_line_user,account.invoice.line stock user,account.model_account_invoice_line,stock.group_stock_user,1,1,1,0
access_account_invoice_tax_user,account.invoice.tax stock user,account.model_account_invoice_tax,stock.group_stock_user,1,1,1,0
diff --git a/addons/stock/stock.py b/addons/stock/stock.py index efd596684cb..8e1180352c2 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -510,9 +510,13 @@ class stock_tracking(osv.osv): return self.name_get(cr, user, ids, context) def name_get(self, cr, uid, ids, context=None): + """Append the serial to the name""" if not len(ids): return [] - res = [(r['id'], r['name']+' ['+(r['serial'] or '')+']') for r in self.read(cr, uid, ids, ['name', 'serial'], context)] + res = [ (r['id'], r['serial'] and '%s [%s]' % (r['name'], r['serial']) + or r['name'] ) + for r in self.read(cr, uid, ids, ['name', 'serial'], + context=context) ] return res def unlink(self, cr, uid, ids, context=None): @@ -644,7 +648,7 @@ class stock_picking(osv.osv): ), 'min_date': fields.function(get_min_max_date, fnct_inv=_set_minimum_date, multi="min_max_date", store=True, type='datetime', string='Scheduled Date', select=1, help="Scheduled date for the shipment to be processed"), - 'date': fields.datetime('Order Date', help="Date of order", select=True, states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}), + 'date': fields.datetime('Date', help="Creation date, usually the date of the order.", select=True, states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}), 'date_done': fields.datetime('Date Done', help="Date of Completion", states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}), 'max_date': fields.function(get_min_max_date, fnct_inv=_set_maximum_date, multi="min_max_date", store=True, type='datetime', string='Max. Expected Date', select=2), @@ -675,6 +679,7 @@ class stock_picking(osv.osv): def action_process(self, cr, uid, ids, context=None): if context is None: context = {} + """Open the partial picking wizard""" context.update({ 'active_model': self._name, 'active_ids': ids, @@ -723,6 +728,7 @@ class stock_picking(osv.osv): return {} def action_explode(self, cr, uid, moves, context=None): + """Hook to allow other modules to split the moves of a picking.""" return moves def action_confirm(self, cr, uid, ids, context=None): @@ -864,14 +870,20 @@ class stock_picking(osv.osv): # TODO: change and create a move if not parents # def action_done(self, cr, uid, ids, context=None): - """ Changes picking state to done. + """Changes picking state to done. + + This method is called at the end of the workflow by the activity "done". @return: True """ self.write(cr, uid, ids, {'state': 'done', 'date_done': time.strftime('%Y-%m-%d %H:%M:%S')}) return True def action_move(self, cr, uid, ids, context=None): - """ Changes move state to assigned. + """Process the Stock Moves of the Picking + + This method is called by the workflow by the activity "move". + Normally that happens when the signal button_done is received (button + "Done" pressed on a Picking view). @return: True """ for pick in self.browse(cr, uid, ids, context=context): @@ -1274,9 +1286,14 @@ class stock_picking(osv.osv): for move in too_few: product_qty = move_product_qty[move.id] if not new_picking: + new_picking_name = pick.name + self.write(cr, uid, [pick.id], + {'name': sequence_obj.get(cr, uid, + 'stock.picking.%s'%(pick.type)), + }) new_picking = self.copy(cr, uid, pick.id, { - 'name': sequence_obj.get(cr, uid, 'stock.picking.%s'%(pick.type)), + 'name': new_picking_name, 'move_lines' : [], 'state':'draft', }) @@ -1296,9 +1313,10 @@ class stock_picking(osv.osv): move_obj.copy(cr, uid, move.id, defaults) move_obj.write(cr, uid, [move.id], { - 'product_qty' : move.product_qty - partial_qty[move.id], + 'product_qty': move.product_qty - partial_qty[move.id], 'product_uos_qty': move.product_qty - partial_qty[move.id], #TODO: put correct uos_qty - + 'prodlot_id': False, + 'tracking_id': False, }) if new_picking: @@ -1343,6 +1361,22 @@ class stock_picking(osv.osv): res[pick.id] = {'delivered_picking': delivered_pack.id or False} return res + + # views associated to each picking type + _VIEW_LIST = { + 'out': 'view_picking_out_form', + 'in': 'view_picking_in_form', + 'internal': 'view_picking_form', + } + def _get_view_id(self, cr, uid, type): + """Get the view id suiting the given type + + @param type: the picking type as a string + @return: view i, or False if no view found + """ + res = self.pool.get('ir.model.data').get_object_reference(cr, uid, + 'stock', self._VIEW_LIST.get(type, 'view_picking_form')) + return res and res[1] or False def log_picking(self, cr, uid, ids, context=None): """ This function will create log messages for picking. @@ -1353,7 +1387,6 @@ class stock_picking(osv.osv): """ if context is None: context = {} - data_obj = self.pool.get('ir.model.data') for pick in self.browse(cr, uid, ids, context=context): msg='' if pick.auto_picking: @@ -1363,11 +1396,6 @@ class stock_picking(osv.osv): 'in':_('Reception'), 'internal': _('Internal picking'), } - view_list = { - 'out': 'view_picking_out_form', - 'in': 'view_picking_in_form', - 'internal': 'view_picking_form', - } message = type_list.get(pick.type, _('Document')) + " '" + (pick.name or '?') + "' " if pick.min_date: msg= _(' for the ')+ datetime.strptime(pick.min_date, '%Y-%m-%d %H:%M:%S').strftime('%m/%d/%Y') @@ -1379,8 +1407,7 @@ class stock_picking(osv.osv): 'auto': _('is waiting.'), 'draft': _('is in draft state.'), } - res = data_obj.get_object_reference(cr, uid, 'stock', view_list.get(pick.type, 'view_picking_form')) - context.update({'view_id': res and res[1] or False}) + context['view_id'] = self._get_view_id(cr, uid, pick.type) message += state_list[pick.state] return True @@ -1620,7 +1647,8 @@ class stock_move(osv.osv): (move.product_id.track_production and move.location_id.usage == 'production') or \ (move.product_id.track_production and move.location_dest_id.usage == 'production') or \ (move.product_id.track_incoming and move.location_id.usage == 'supplier') or \ - (move.product_id.track_outgoing and move.location_dest_id.usage == 'customer') \ + (move.product_id.track_outgoing and move.location_dest_id.usage == 'customer') or \ + (move.product_id.track_incoming and move.location_id.usage == 'inventory') \ )): return False return True @@ -1642,7 +1670,16 @@ class stock_move(osv.osv): 'date_expected': fields.datetime('Scheduled Date', states={'done': [('readonly', True)]},required=True, select=True, help="Scheduled date for the processing of this move"), 'product_id': fields.many2one('product.product', 'Product', required=True, select=True, domain=[('type','<>','service')],states={'done': [('readonly', True)]}), - 'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True,states={'done': [('readonly', True)]}), + 'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), + required=True,states={'done': [('readonly', True)]}, + help="This is the quantity of products from an inventory " + "point of view. For moves in the state 'done', this is the " + "quantity of products that were actually moved. For other " + "moves, this is the quantity of product that is planned to " + "be moved. Lowering this quantity does not generate a " + "backorder. Changing this quantity on assigned moves affects " + "the product reservation, and should be done with care." + ), 'product_uom': fields.many2one('product.uom', 'Unit of Measure', required=True,states={'done': [('readonly', True)]}), 'product_uos_qty': fields.float('Quantity (UOS)', digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]}), 'product_uos': fields.many2one('product.uom', 'Product UOS', states={'done': [('readonly', True)]}), @@ -1677,7 +1714,7 @@ class stock_move(osv.osv): 'price_unit': fields.float('Unit Price', digits_compute= dp.get_precision('Account'), help="Technical field used to record the product cost set by the user during a picking confirmation (when average price costing method is used)"), 'price_currency_id': fields.many2one('res.currency', 'Currency for average price', help="Technical field used to record the currency chosen by the user during a picking confirmation (when average price costing method is used)"), 'company_id': fields.many2one('res.company', 'Company', required=True, select=True), - 'backorder_id': fields.related('picking_id','backorder_id',type='many2one', relation="stock.picking", string="Back Order", select=True), + 'backorder_id': fields.related('picking_id','backorder_id',type='many2one', relation="stock.picking", string="Back Order of", select=True), 'origin': fields.related('picking_id','origin',type='char', size=64, relation="stock.picking", string="Source", store=True), # used for colors in tree views: @@ -1863,19 +1900,32 @@ class stock_move(osv.osv): result = { 'product_uos_qty': 0.00 } + warning = {} if (not product_id) or (product_qty <=0.0): + result['product_qty'] = 0.0 return {'value': result} product_obj = self.pool.get('product.product') uos_coeff = product_obj.read(cr, uid, product_id, ['uos_coeff']) + + # Warn if the quantity was decreased + if ids: + for move in self.read(cr, uid, ids, ['product_qty']): + if product_qty < move['product_qty']: + warning.update({ + 'title': _('Information'), + 'message': _("By changing this quantity here, you accept the " + "new quantity as complete: OpenERP will not " + "automatically generate a back order.") }) + break if product_uos and product_uom and (product_uom != product_uos): result['product_uos_qty'] = product_qty * uos_coeff['uos_coeff'] else: result['product_uos_qty'] = product_qty - return {'value': result} + return {'value': result, 'warning': warning} def onchange_uos_quantity(self, cr, uid, ids, product_id, product_uos_qty, product_uos, product_uom): @@ -1889,19 +1939,30 @@ class stock_move(osv.osv): result = { 'product_qty': 0.00 } + warning = {} if (not product_id) or (product_uos_qty <=0.0): + result['product_uos_qty'] = 0.0 return {'value': result} product_obj = self.pool.get('product.product') uos_coeff = product_obj.read(cr, uid, product_id, ['uos_coeff']) + + # Warn if the quantity was decreased + for move in self.read(cr, uid, ids, ['product_uos_qty']): + if product_uos_qty < move['product_uos_qty']: + warning.update({ + 'title': _('Warning: No Back Order'), + 'message': _("By changing the quantity here, you accept the " + "new quantity as complete: OpenERP will not " + "automatically generate a Back Order.") }) + break if product_uos and product_uom and (product_uom != product_uos): result['product_qty'] = product_uos_qty / uos_coeff['uos_coeff'] else: result['product_qty'] = product_uos_qty - - return {'value': result} + return {'value': result, 'warning': warning} def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False, loc_dest_id=False, partner_id=False): @@ -1927,7 +1988,8 @@ class stock_move(osv.osv): 'product_uom': product.uom_id.id, 'product_uos': uos_id, 'product_qty': 1.00, - 'product_uos_qty' : self.pool.get('stock.move').onchange_quantity(cr, uid, ids, prod_id, 1.00, product.uom_id.id, uos_id)['value']['product_uos_qty'] + 'product_uos_qty' : self.pool.get('stock.move').onchange_quantity(cr, uid, ids, prod_id, 1.00, product.uom_id.id, uos_id)['value']['product_uos_qty'], + 'prodlot_id' : False, } if not ids: result['name'] = product.partner_ref @@ -2114,6 +2176,10 @@ class stock_move(osv.osv): @return: True """ self.write(cr, uid, ids, {'state': 'assigned'}) + wf_service = netsvc.LocalService('workflow') + for move in self.browse(cr, uid, ids, context): + if move.picking_id: + wf_service.trg_write(uid, 'stock.picking', move.picking_id.id, cr) return True def cancel_assign(self, cr, uid, ids, context=None): @@ -2490,8 +2556,6 @@ class stock_move(osv.osv): 'tracking_id': move.tracking_id.id, 'prodlot_id': move.prodlot_id.id, } - if move.location_id.usage <> 'internal': - default_val.update({'location_id': move.location_dest_id.id}) new_move = self.copy(cr, uid, move.id, default_val) res += [new_move] @@ -2505,6 +2569,7 @@ class stock_move(osv.osv): return res # action_split function is not used anywhere + # FIXME: deprecate this method def action_split(self, cr, uid, ids, quantity, split_by_qty=1, prefix=False, with_lot=True, context=None): """ Split Stock Move lines into production lot which specified split by quantity. @param cr: the database cursor @@ -2709,8 +2774,10 @@ class stock_move(osv.osv): complete.append(self.browse(cr, uid, new_move)) self.write(cr, uid, [move.id], { - 'product_qty' : move.product_qty - product_qty, - 'product_uos_qty':move.product_qty - product_qty, + 'product_qty': move.product_qty - product_qty, + 'product_uos_qty': move.product_qty - product_qty, + 'prodlot_id': False, + 'tracking_id': False, }) @@ -2749,7 +2816,7 @@ class stock_inventory(osv.osv): 'name': fields.char('Inventory Reference', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}), 'date': fields.datetime('Creation Date', required=True, readonly=True, states={'draft': [('readonly', False)]}), 'date_done': fields.datetime('Date done'), - 'inventory_line_id': fields.one2many('stock.inventory.line', 'inventory_id', 'Inventories', states={'done': [('readonly', True)]}), + 'inventory_line_id': fields.one2many('stock.inventory.line', 'inventory_id', 'Inventories', readonly=True, states={'draft': [('readonly', False)]}), 'move_ids': fields.many2many('stock.move', 'stock_inventory_move_rel', 'inventory_id', 'move_id', 'Created Moves'), 'state': fields.selection( (('draft', 'Draft'), ('cancel','Cancelled'), ('confirm','Confirmed'), ('done', 'Done')), 'Status', readonly=True, select=True), 'company_id': fields.many2one('res.company', 'Company', required=True, select=True, readonly=True, states={'draft':[('readonly',False)]}), @@ -2896,11 +2963,11 @@ class stock_inventory_line(osv.osv): @return: Dictionary of changed values """ if not product: - return {'value': {'product_qty': 0.0, 'product_uom': False}} + return {'value': {'product_qty': 0.0, 'product_uom': False, 'prod_lot_id': False}} obj_product = self.pool.get('product.product').browse(cr, uid, product) uom = uom or obj_product.uom_id.id amount = self.pool.get('stock.location')._product_get(cr, uid, location_id, [product], {'uom': uom, 'to_date': to_date, 'compute_child': False})[product] - result = {'product_qty': amount, 'product_uom': uom} + result = {'product_qty': amount, 'product_uom': uom, 'prod_lot_id': False} return {'value': result} stock_inventory_line() @@ -2945,7 +3012,7 @@ class stock_picking_in(osv.osv): _name = "stock.picking.in" _inherit = "stock.picking" _table = "stock_picking" - _description = "Incomming Shipments" + _description = "Incoming Shipments" def check_access_rights(self, cr, uid, operation, raise_exception=True): #override in order to redirect the check of acces rights on the stock.picking object diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 83fed186f42..d991e811777 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -759,26 +759,26 @@ <field name="state" widget="statusbar" statusbar_visible="draft,assigned,done" statusbar_colors='{"shipping_except":"red","invoice_except":"red","waiting_date":"blue"}'/> </header> <sheet> - <label for="name" class="oe_edit_only"/> <h1> - <field name="name" class="oe_inline"/> + <field name="name" class="oe_inline" attrs="{'invisible': [('name','=','/')]}" readonly="1"/> </h1> <group> <group> <field name="partner_id" on_change="onchange_partner_in(partner_id)"/> - <field name="backorder_id" readonly="1"/> + <field name="backorder_id" readonly="1" attrs="{'invisible': [('backorder_id','=',False)]}"/> <field name="invoice_state" string="Invoice Control" groups="account.group_account_invoice" attrs="{'invisible':[('invoice_state', '=', 'none')]}"/> <field name="stock_journal_id" widget="selection" groups="account.group_account_user"/> </group> <group> <field name="date"/> - <field name="min_date" readonly="1"/> + <field name="min_date" readonly="1" attrs="{'invisible': [('min_date','=',False)]}"/> <field name="origin" placeholder="e.g. PO0032" class="oe_inline"/> </group> </group> <notebook> <page string="Products"> <field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree'}"/> + <field name="note" placeholder="Add an internal note..." class="oe_inline"/> </page> <page string="Additional Info"> <group> @@ -793,9 +793,6 @@ </group> </group> </page> - <page string="Notes"> - <field name="note" placeholder="Add an internal note..."/> - </page> </notebook> </sheet> </form> @@ -913,10 +910,11 @@ <field name="partner_id" on_change="onchange_partner_in(partner_id)" string="Customer"/> </xpath> <xpath expr="//field[@name='move_lines']" position="replace"> - <field name="move_lines" context="{'address_in_id': partner_id, 'picking_type': 'out', 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree'}"/> + <field name="move_lines" context="{'address_out_id': partner_id, 'picking_type': 'out', 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree'}"/> </xpath> <xpath expr="/form/sheet" position="after"> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -1043,6 +1041,7 @@ </xpath> <xpath expr="/form/sheet" position="after"> <div class="oe_chatter"> + <field name="message_is_follower" invisible="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_follower_ids" widget="mail_followers"/> </div> @@ -1120,7 +1119,7 @@ <record id="view_move_tree" model="ir.ui.view"> <field name="name">stock.move.tree</field> <field name="model">stock.move</field> - <field eval="6" name="priority"/> + <field eval="8" name="priority"/> <field name="arch" type="xml"> <tree colors="grey:state == 'cancel';red:(state not in ('cancel','done')) and date > current_date" string="Moves" editable="top"> <field name="name"/> @@ -1494,7 +1493,7 @@ <field name="view_id" ref="view_move_tree_reception_picking"/> <field name="context">{'product_receive': True, 'search_default_future': True, 'picking_type': 'in'}</field> <field name="help" type="html"> - <p> + <p class="oe_view_nocontent_create"> Click to register a product reception. </p><p> Here you can receive individual products, no matter what @@ -1629,6 +1628,9 @@ <field name="view_id" ref="view_move_tree_reception_picking"/> <field name="context">{'picking_type': 'out', 'search_default_future': True}</field> <field name="help" type="html"> + <p class="oe_view_nocontent_create"> + Click to register a product delivery. + </p> <p> You will find in this list all products you have to deliver to your customers. You can process the deliveries directly from @@ -1694,7 +1696,7 @@ <field name="type">ir.actions.act_window</field> <field name="view_type">form</field> <field name="help" type="html"> - <p> + <p class="oe_view_nocontent_create"> Click to create a new journal. </p><p> The stock journal system allows you to assign each stock diff --git a/addons/stock/wizard/stock_fill_inventory.py b/addons/stock/wizard/stock_fill_inventory.py index 9b730d22982..11907b674a1 100644 --- a/addons/stock/wizard/stock_fill_inventory.py +++ b/addons/stock/wizard/stock_fill_inventory.py @@ -109,16 +109,17 @@ class stock_fill_inventory(osv.osv_memory): for move in move_obj.browse(cr, uid, move_ids, context=context): lot_id = move.prodlot_id.id prod_id = move.product_id.id - if move.location_dest_id.id == location: - qty = uom_obj._compute_qty(cr, uid, move.product_uom.id,move.product_qty, move.product_id.uom_id.id) - else: - qty = -uom_obj._compute_qty(cr, uid, move.product_uom.id,move.product_qty, move.product_id.uom_id.id) + if move.location_dest_id.id != move.location_id.id: + if move.location_dest_id.id == location: + qty = uom_obj._compute_qty(cr, uid, move.product_uom.id,move.product_qty, move.product_id.uom_id.id) + else: + qty = -uom_obj._compute_qty(cr, uid, move.product_uom.id,move.product_qty, move.product_id.uom_id.id) - if datas.get((prod_id, lot_id)): - qty += datas[(prod_id, lot_id)]['product_qty'] + if datas.get((prod_id, lot_id)): + qty += datas[(prod_id, lot_id)]['product_qty'] - datas[(prod_id, lot_id)] = {'product_id': prod_id, 'location_id': location, 'product_qty': qty, 'product_uom': move.product_id.uom_id.id, 'prod_lot_id': lot_id} + datas[(prod_id, lot_id)] = {'product_id': prod_id, 'location_id': location, 'product_qty': qty, 'product_uom': move.product_id.uom_id.id, 'prod_lot_id': lot_id} if datas: flag = True diff --git a/addons/stock/wizard/stock_partial_picking.py b/addons/stock/wizard/stock_partial_picking.py index 2cf0a743faa..61ae519f65f 100644 --- a/addons/stock/wizard/stock_partial_picking.py +++ b/addons/stock/wizard/stock_partial_picking.py @@ -65,6 +65,7 @@ class stock_partial_picking_line(osv.TransientModel): class stock_partial_picking(osv.osv_memory): _name = "stock.partial.picking" + _rec_name = 'picking_id' _description = "Partial Picking Processing Wizard" def _hide_tracking(self, cursor, user, ids, name, arg, context=None): diff --git a/addons/subscription/subscription.py b/addons/subscription/subscription.py index 45916e04aee..25137f6303c 100644 --- a/addons/subscription/subscription.py +++ b/addons/subscription/subscription.py @@ -72,7 +72,7 @@ class subscription_subscription(osv.osv): 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status'), 'doc_source': fields.reference('Source Document', required=True, selection=_get_document_types, size=128, help="User can choose the source document on which he wants to create documents"), 'doc_lines': fields.one2many('subscription.subscription.history', 'subscription_id', 'Documents created', readonly=True), - 'cron_id': fields.many2one('ir.cron', 'Cron Job', help="Scheduler which runs on subscription"), + 'cron_id': fields.many2one('ir.cron', 'Cron Job', help="Scheduler which runs on subscription", states={'running':[('readonly',True)], 'done':[('readonly',True)]}), 'note': fields.text('Notes', help="Description or Summary of Subscription"), } _defaults = { diff --git a/addons/survey/survey.py b/addons/survey/survey.py index 2426ae5eeed..f618a95cccf 100644 --- a/addons/survey/survey.py +++ b/addons/survey/survey.py @@ -145,10 +145,14 @@ class survey(osv.osv): return report def fill_survey(self, cr, uid, ids, context=None): - sur_obj = self.read(cr, uid, ids,['title'], context=context) + sur_obj = self.read(cr, uid, ids,['title', 'page_ids'], context=context) for sur in sur_obj: name = sur['title'] - context.update({'active':False,'survey_id': ids[0]}) + pages = sur['page_ids'] + if not pages: + raise osv.except_osv(_('Warning!'), _('This survey has no question defined. Please define the questions and answers first.')) + else: + context.update({'active':False,'survey_id': ids[0]}) return { 'view_type': 'form', 'view_mode': 'form', |
