diff --git a/addons/account/account_installer.xml b/addons/account/account_installer.xml index e8937f689a3..b720ea16922 100644 --- a/addons/account/account_installer.xml +++ b/addons/account/account_installer.xml @@ -9,9 +9,9 @@
Accounting Application Configuration
- + diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 6a0a29cb2fe..b1c5db23df6 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -306,7 +306,7 @@ class account_invoice(osv.osv): if view_type == 'form': if partner['supplier'] and not partner['customer']: view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name', '=', 'account.invoice.supplier.form')]) - else: + elif partner['customer'] and not partner['supplier']: view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name', '=', 'account.invoice.form')]) if view_id and isinstance(view_id, (list, tuple)): view_id = view_id[0] diff --git a/addons/account/company_view.xml b/addons/account/company_view.xml index 4ade4b90876..05e1c77edea 100644 --- a/addons/account/company_view.xml +++ b/addons/account/company_view.xml @@ -25,7 +25,7 @@ - + diff --git a/addons/account/installer.py b/addons/account/installer.py index 5a2b0b346c4..4fc4754ea3f 100644 --- a/addons/account/installer.py +++ b/addons/account/installer.py @@ -119,6 +119,15 @@ class account_installer(osv.osv_memory): self.execute_simple(cr, uid, ids, context) super(account_installer, self).execute(cr, uid, ids, context=context) + def action_next(self, cr, uid, ids, context=None): + next = self.execute(cr, uid, ids, context=context) + for installer in self.browse(cr, uid, ids, context=context): + if installer.charts == 'l10n_be': + return {'type': 'ir.actions.act_window_close'} + else : + if next : return next + return self.next(cr, uid, ids, context=context) + def execute_simple(self, cr, uid, ids, context=None): if context is None: context = {} diff --git a/addons/account/res_config.py b/addons/account/res_config.py index 120d901a006..658f87962ff 100644 --- a/addons/account/res_config.py +++ b/addons/account/res_config.py @@ -79,10 +79,10 @@ class account_config_settings(osv.osv_memory): 'purchase_refund_sequence_prefix': fields.related('purchase_refund_journal_id', 'sequence_id', 'prefix', type='char', string='Supplier credit note sequence'), 'purchase_refund_sequence_next': fields.related('purchase_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next supplier credit note number'), - 'module_account_check_writing': fields.boolean('pay your supplier by check', + 'module_account_check_writing': fields.boolean('pay your suppliers by check', help="""This allows you to check writing and printing. This installs the module account_check_writing."""), - 'module_account_accountant': fields.boolean('accountant features', + 'module_account_accountant': fields.boolean('full accounting features: journals, legal statements, chart of accounts, etc.', help="""If you do not check this box, you will be able to do invoicing & payments, but not accounting (Journal Items, Chart of Accounts, ...)"""), 'module_account_asset': fields.boolean('assets management', help="""This allows you to manage the assets owned by a company or a person. @@ -99,7 +99,7 @@ class account_config_settings(osv.osv_memory): * serve as base for an easy plug-in of various automated payment mechanisms, and * provide a more efficient way to manage invoice payments. This installs the module account_payment."""), - 'module_account_voucher': fields.boolean('anage customer payments', + 'module_account_voucher': fields.boolean('manage customer payments', help="""This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc. This installs the module account_voucher."""), 'module_account_followup': fields.boolean('manage customer payment follow-ups', diff --git a/addons/account/res_config_view.xml b/addons/account/res_config_view.xml index fd0fa552238..d19a5dc5f98 100644 --- a/addons/account/res_config_view.xml +++ b/addons/account/res_config_view.xml @@ -103,6 +103,39 @@ + + + - - - diff --git a/addons/account_bank_statement_extensions/i18n/tr.po b/addons/account_bank_statement_extensions/i18n/tr.po new file mode 100644 index 00000000000..4adfe7f304f --- /dev/null +++ b/addons/account_bank_statement_extensions/i18n/tr.po @@ -0,0 +1,382 @@ +# Turkish translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-02-08 00:35+0000\n" +"PO-Revision-Date: 2012-08-05 14:35+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Turkish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2012-08-06 04:58+0000\n" +"X-Generator: Launchpad (build 15745)\n" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Search Bank Transactions" +msgstr "Banka İşlemi Ara" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +#: selection:account.bank.statement.line,state:0 +msgid "Confirmed" +msgstr "Onaylandı" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement:0 +#: view:account.bank.statement.line:0 +msgid "Glob. Id" +msgstr "Glob. Id" + +#. module: account_bank_statement_extensions +#: selection:account.bank.statement.line.global,type:0 +msgid "CODA" +msgstr "CODA" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line.global,parent_id:0 +msgid "Parent Code" +msgstr "Ana Kod" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Debit" +msgstr "Borç" + +#. module: account_bank_statement_extensions +#: view:cancel.statement.line:0 +#: model:ir.actions.act_window,name:account_bank_statement_extensions.action_cancel_statement_line +#: model:ir.model,name:account_bank_statement_extensions.model_cancel_statement_line +msgid "Cancel selected statement lines" +msgstr "Seçili hesap özeti satırlarını sil" + +#. module: account_bank_statement_extensions +#: constraint:res.partner.bank:0 +msgid "The RIB and/or IBAN is not valid" +msgstr "RIB ve/veya IBAN geçerli değil" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Group By..." +msgstr "Gruplandır..." + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line,state:0 +msgid "State" +msgstr "Durum" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +#: selection:account.bank.statement.line,state:0 +msgid "Draft" +msgstr "Taslak" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Statement" +msgstr "Hesap Özeti" + +#. module: account_bank_statement_extensions +#: view:confirm.statement.line:0 +#: model:ir.actions.act_window,name:account_bank_statement_extensions.action_confirm_statement_line +#: model:ir.model,name:account_bank_statement_extensions.model_confirm_statement_line +msgid "Confirm selected statement lines" +msgstr "Seçili hesap özeti satırlarını onayla" + +#. module: account_bank_statement_extensions +#: report:bank.statement.balance.report:0 +#: model:ir.actions.report.xml,name:account_bank_statement_extensions.bank_statement_balance_report +msgid "Bank Statement Balances Report" +msgstr "Banka Durumu Raporu" + +#. module: account_bank_statement_extensions +#: view:cancel.statement.line:0 +msgid "Cancel Lines" +msgstr "Satırları İptal et" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line.global:0 +#: model:ir.model,name:account_bank_statement_extensions.model_account_bank_statement_line_global +msgid "Batch Payment Info" +msgstr "Toplu Ödeme Bilgisi" + +#. module: account_bank_statement_extensions +#: view:confirm.statement.line:0 +msgid "Confirm Lines" +msgstr "Satırları Onayla" + +#. module: account_bank_statement_extensions +#: code:addons/account_bank_statement_extensions/account_bank_statement.py:130 +#, python-format +msgid "" +"Delete operation not allowed ! Please go to the associated bank " +"statement in order to delete and/or modify this bank statement line" +msgstr "" +"İşlemin silinmesine izin verilmedi! Bu banka hesap özeti satırını silmek/ ya " +"da değiştirmek için bağlantılı banka hesap özetine gidin" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line.global,type:0 +msgid "Type" +msgstr "Tür" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +#: field:account.bank.statement.line,journal_id:0 +#: report:bank.statement.balance.report:0 +msgid "Journal" +msgstr "Günlük" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Confirmed Statement Lines." +msgstr "Onayl Hesap Özeti Satırları." + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Credit Transactions." +msgstr "Krdei İşlemleri" + +#. module: account_bank_statement_extensions +#: model:ir.actions.act_window,help:account_bank_statement_extensions.action_cancel_statement_line +msgid "cancel selected statement lines." +msgstr "Seçili hesap özeti satırlarını iptal et." + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line,counterparty_number:0 +msgid "Counterparty Number" +msgstr "Karşı Taraf Numarası" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line.global:0 +msgid "Transactions" +msgstr "İşlemler" + +#. module: account_bank_statement_extensions +#: code:addons/account_bank_statement_extensions/account_bank_statement.py:130 +#, python-format +msgid "Warning" +msgstr "Uyarı" + +#. module: account_bank_statement_extensions +#: report:bank.statement.balance.report:0 +msgid "Closing Balance" +msgstr "Kapanış Bakiyesi" + +#. module: account_bank_statement_extensions +#: report:bank.statement.balance.report:0 +msgid "Date" +msgstr "Tarih" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +#: field:account.bank.statement.line,globalisation_amount:0 +msgid "Glob. Amount" +msgstr "Glob. Tutar" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Debit Transactions." +msgstr "Borç İşlemleri." + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Extended Filters..." +msgstr "Genişletilmiş Süzgeçler..." + +#. module: account_bank_statement_extensions +#: view:confirm.statement.line:0 +msgid "Confirmed lines cannot be changed anymore." +msgstr "Onaylı satırlar artık değiştirilemez." + +#. module: account_bank_statement_extensions +#: constraint:res.partner.bank:0 +msgid "" +"\n" +"Please define BIC/Swift code on bank for bank type IBAN Account to make " +"valid payments" +msgstr "" +"\n" +"IBAN hesap tipli bankalara geçerli ödeme yapabilmek için lütfen bankanın " +"BIC/SWIFT kodunu tanımlayın" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line,val_date:0 +msgid "Valuta Date" +msgstr "Efektif Tarih" + +#. module: account_bank_statement_extensions +#: model:ir.actions.act_window,help:account_bank_statement_extensions.action_confirm_statement_line +msgid "Confirm selected statement lines." +msgstr "Seçili hesap özeti satırlarını onayla." + +#. module: account_bank_statement_extensions +#: view:cancel.statement.line:0 +msgid "Are you sure you want to cancel the selected Bank Statement lines ?" +msgstr "" +"Seçili Banka Hesap Özeti satırlarını iptal etmek istediğinizden emin misiniz?" + +#. module: account_bank_statement_extensions +#: report:bank.statement.balance.report:0 +msgid "Name" +msgstr "Adı" + +#. module: account_bank_statement_extensions +#: selection:account.bank.statement.line.global,type:0 +msgid "ISO 20022" +msgstr "ISO 20022" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Notes" +msgstr "Notlar" + +#. module: account_bank_statement_extensions +#: selection:account.bank.statement.line.global,type:0 +msgid "Manual" +msgstr "El ile" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Credit" +msgstr "Alacak" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line.global,amount:0 +msgid "Amount" +msgstr "Tutar" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Fin.Account" +msgstr "Fin.Hesap" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line,counterparty_currency:0 +msgid "Counterparty Currency" +msgstr "Karşı Taraf Para Birimi" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line,counterparty_bic:0 +msgid "Counterparty BIC" +msgstr "Karşı Tarafı BIC" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line.global,child_ids:0 +msgid "Child Codes" +msgstr "Alt Kodlar" + +#. module: account_bank_statement_extensions +#: view:confirm.statement.line:0 +msgid "Are you sure you want to confirm the selected Bank Statement lines ?" +msgstr "" +"Seçili Banka Hesap Özeti satırlarını onaylamak istediğinizden emin misiniz?" + +#. module: account_bank_statement_extensions +#: constraint:account.bank.statement.line:0 +msgid "" +"The amount of the voucher must be the same amount as the one on the " +"statement line" +msgstr "Fiş tutarı banka hesap özetindekiyle aynı tutarda olmalı" + +#. module: account_bank_statement_extensions +#: help:account.bank.statement.line,globalisation_id:0 +msgid "" +"Code to identify transactions belonging to the same globalisation level " +"within a batch payment" +msgstr "" +"Bir toplu ödemede aynı küreselleşme seviyesine ait işlemlerin tanımlama Kodu" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Draft Statement Lines." +msgstr "Taslak Hesap Özeti satırları" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Glob. Am." +msgstr "Glob. Am." + +#. module: account_bank_statement_extensions +#: model:ir.model,name:account_bank_statement_extensions.model_account_bank_statement_line +msgid "Bank Statement Line" +msgstr "Banka Hesap Özeti Satırı" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line.global,code:0 +msgid "Code" +msgstr "Kod" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line,counterparty_name:0 +msgid "Counterparty Name" +msgstr "Karşı Taraf Adı" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line.global,name:0 +msgid "Communication" +msgstr "İletişim" + +#. module: account_bank_statement_extensions +#: model:ir.model,name:account_bank_statement_extensions.model_res_partner_bank +msgid "Bank Accounts" +msgstr "Banka Hesapları" + +#. module: account_bank_statement_extensions +#: constraint:account.bank.statement:0 +msgid "The journal and period chosen have to belong to the same company." +msgstr "Seçilen günlük ve dönem aynı firmaya ait olmalıdır." + +#. module: account_bank_statement_extensions +#: model:ir.model,name:account_bank_statement_extensions.model_account_bank_statement +msgid "Bank Statement" +msgstr "Banka Hesap Özeti" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Statement Line" +msgstr "Hesap Özeti Satırı" + +#. module: account_bank_statement_extensions +#: sql_constraint:account.bank.statement.line.global:0 +msgid "The code must be unique !" +msgstr "Kod eşsiz olamlı!" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line.global,bank_statement_line_ids:0 +#: model:ir.actions.act_window,name:account_bank_statement_extensions.action_bank_statement_line +#: model:ir.ui.menu,name:account_bank_statement_extensions.bank_statement_line +msgid "Bank Statement Lines" +msgstr "Banka Hesap Özeti Satırları" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line.global:0 +msgid "Child Batch Payments" +msgstr "Alt Toplu Ödemeler" + +#. module: account_bank_statement_extensions +#: view:cancel.statement.line:0 +#: view:confirm.statement.line:0 +msgid "Cancel" +msgstr "Vazgeç" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Statement Lines" +msgstr "Hesap Özeti Satırları" + +#. module: account_bank_statement_extensions +#: view:account.bank.statement.line:0 +msgid "Total Amount" +msgstr "Toplam Tutar" + +#. module: account_bank_statement_extensions +#: field:account.bank.statement.line,globalisation_id:0 +msgid "Globalisation ID" +msgstr "Küreselleşme ID" diff --git a/addons/account_voucher/voucher_payment_receipt_view.xml b/addons/account_voucher/voucher_payment_receipt_view.xml index e85f06aeba3..99ea3a2bbea 100644 --- a/addons/account_voucher/voucher_payment_receipt_view.xml +++ b/addons/account_voucher/voucher_payment_receipt_view.xml @@ -152,8 +152,8 @@ - - + + @@ -318,8 +318,8 @@ - - + + diff --git a/addons/base_vat/base_vat_view.xml b/addons/base_vat/base_vat_view.xml index df1d18ac194..ec98b7922de 100644 --- a/addons/base_vat/base_vat_view.xml +++ b/addons/base_vat/base_vat_view.xml @@ -10,7 +10,7 @@ + + mail.catchall.domain + demo.openerp.com + + diff --git a/addons/mail/mail_alias.py b/addons/mail/mail_alias.py new file mode 100644 index 00000000000..807ed5dbe61 --- /dev/null +++ b/addons/mail/mail_alias.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (C) 2012 OpenERP S.A. (). +# +# 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 . +# +############################################################################## + +import re + +from openerp.osv import fields, osv + + +class mail_alias(osv.Model): + """A Mail Alias is a mapping of an email address with a given OpenERP Document + model. It is used by OpenERP's mail gateway when processing incoming emails + sent to the system. If the recipient address (To) of the message matches + a Mail Alias, the message will be either processed following the rules + of that alias. If the message is a reply it will be attached to the + existing discussion on the corresponding record, otherwise a new + record of the corresponding model will be created. + + This is meant to be used in combination with a catch-all email configuration + on the company's mail server, so that as soon as a new mail.alias is + created, it becomes immediately usable and OpenERP will accept email for it. + """ + _name = 'mail.alias' + _description = "Email Aliases" + _rec_name = 'alias_name' + + def _get_alias_domain(self, cr, uid, ids, name, args, context=None): + ir_config_parameter = self.pool.get("ir.config_parameter") + domain = ir_config_parameter.get_param(cr, uid, "mail.catchall.domain", context=context) + return dict.fromkeys(ids, domain or "") + + _columns = { + 'alias_name': fields.char('Alias', required=True, + help="The name of the email alias, e.g. 'jobs' " + "if you want to catch emails for ",), + 'alias_model_id': fields.many2one('ir.model', 'Aliased Model', required=True, + help="The model (OpenERP Document Kind) to which this alias " + "corresponds. Any incoming email that does not reply to an " + "existing record will cause the creation of a new record " + "of this model (e.g. a Project Task)", + # only allow selecting mail_thread models! + #TODO kept doamin temporarily in comment, need to redefine domain + #domain="[('field_id', 'in', 'message_ids')]" + ), + 'alias_user_id': fields.many2one('res.users', 'Owner', + help="The owner of records created upon receiving emails on this alias. " + "If this field is not set the system will attempt to find the right owner " + "based on the sender (From) address, or will use the Administrator account " + "if no system user is found for that address."), + 'alias_defaults': fields.text('Default Values', required=True, + help="A Python dictionary that will be evaluated to provide " + "default values when creating new records for this alias."), + 'alias_force_thread_id': fields.integer('Record Thread ID', + help="Optional ID of a thread (record) to which all incoming " + "messages will be attached, even if they did not reply to it. " + "If set, this will disable the creation of new records completely."), + 'alias_domain': fields.function(_get_alias_domain, string="Alias Domain", type='char', size=None), + } + + _defaults = { + 'alias_defaults': '{}', + 'alias_user_id': lambda self,cr,uid, context: uid + } + + _sql_constraints = [ + ('alias_unique', 'UNIQUE(alias_name)', 'Unfortunately this email alias is already used, please choose a unique one') + ] + + def _check_alias_defaults(self, cr, uid, ids, context=None): + try: + for record in self.browse(cr, uid, ids, context=context): + dict(eval(record.alias_defaults)) + except Exception: + return False + return True + + _constraints = [ + (_check_alias_defaults, '''Invalid expression, it must be a literal python dictionary definition e.g. "{'field': 'value'}"''', ['alias_defaults']), + ] + + def name_get(self, cr, uid, ids, context=None): + """Return the mail alias display alias_name, inclusing the implicit + mail catchall domain from config. + e.g. `jobs@openerp.my.openerp.com` or `sales@openerp.my.openerp.com` + """ + return [(record['id'], "%s@%s" % (record['alias_name'], record['alias_domain'])) + for record in self.read(cr, uid, ids, ['alias_name', 'alias_domain'], context=context)] + + def _find_unique(self, cr, uid, name, context=None): + """Find a unique alias name similar to ``name``. If ``name`` is + already taken, make a variant by adding an integer suffix until + an unused alias is found. + """ + sequence = None + while True: + new_name = "%s%s" % (name, sequence) if sequence is not None else name + if not self.search(cr, uid, [('alias_name', '=', new_name)]): + break + sequence = (sequence + 1) if sequence else 2 + return new_name + + def create_unique_alias(self, cr, uid, vals, model_name=None, context=None): + """Creates an email.alias record according to the values provided in ``vals``, + with 2 alterations: the ``alias_name`` value may be suffixed in order to + make it unique, and the ``alias_model_id`` value will set to the + model ID of the ``model_name`` value, if provided, + """ + alias_name = re.sub(r'\W+', '_', vals['alias_name']).lower() + alias_name = self._find_unique(cr, uid, alias_name, context=context) + vals['alias_name'] = alias_name + if model_name: + model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', model_name)], context=context)[0] + vals['alias_model_id'] = model_id + return self.create(cr, uid, vals, context=context) + + diff --git a/addons/mail/mail_alias_view.xml b/addons/mail/mail_alias_view.xml new file mode 100644 index 00000000000..61a89b774ca --- /dev/null +++ b/addons/mail/mail_alias_view.xml @@ -0,0 +1,72 @@ + + + + + + + mail.alias.form + mail.alias + form + + + + + + + + + + + mail.alias.tree + mail.alias + tree + + + + + + + + + + + + + mail.alias.search + mail.alias + search + + + + + + + + + + + + + + Aliases + mail.alias + + + + + + diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index 36001ee6074..19fe00107bd 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -30,6 +30,7 @@ from PIL import Image import StringIO import tools from tools.translate import _ +from lxml import etree class mail_group(osv.osv): """ @@ -48,6 +49,7 @@ class mail_group(osv.osv): _description = 'Discussion group' _name = 'mail.group' _inherit = ['mail.thread'] + _inherits = {'mail.alias': 'alias_id'} def onchange_photo(self, cr, uid, ids, value, context=None): if not value: @@ -137,6 +139,9 @@ class mail_group(osv.osv): string='Joined', multi='get_member_ids'), 'last_month_msg_nbr': fields.function(get_last_month_msg_nbr, type='integer', string='Messages count for last month'), + 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True, + help="The email address associated with this group. New emails received will automatically " + "create new topics."), } _defaults = { @@ -158,11 +163,29 @@ class mail_group(osv.osv): return self.message_subscribe(cr, uid, ids, user_ids, context=context) def create(self, cr, uid, vals, context=None): - mail_group_id = super(mail_group, self).create(cr, uid, vals, context=context) + alias_pool = self.pool.get('mail.alias') + if not vals.get('alias_id'): + name = vals.get('alias_name') or vals['name'] + alias_id = alias_pool.create_unique_alias(cr, uid, + {'alias_name': "group_"+name}, + model_name=self._name, context=context) + vals['alias_id'] = alias_id + mail_group_id = super(mail_group, self).create(cr, uid, vals, context) + alias_pool.write(cr, uid, [vals['alias_id']], {"alias_force_thread_id": mail_group_id}, context) + if vals.get('group_ids'): self._subscribe_user_with_group_m2m_command(cr, uid, [mail_group_id], vals.get('group_ids'), context=context) + return mail_group_id + def unlink(self, cr, uid, ids, context=None): + # Cascade-delete mail aliases as well, as they should not exist without the mail group. + mail_alias = self.pool.get('mail.alias') + alias_ids = [group.alias_id.id for group in self.browse(cr, uid, ids, context=context) if group.alias_id] + res = super(mail_group, self).unlink(cr, uid, ids, context=context) + mail_alias.unlink(cr, uid, alias_ids, context=context) + return res + def write(self, cr, uid, ids, vals, context=None): if vals.get('group_ids'): self._subscribe_user_with_group_m2m_command(cr, uid, ids, vals.get('group_ids'), context=context) @@ -170,6 +193,6 @@ class mail_group(osv.osv): def action_group_join(self, cr, uid, ids, context=None): return self.message_subscribe(cr, uid, ids, context=context) - + def action_group_leave(self, cr, uid, ids, context=None): return self.message_unsubscribe(cr, uid, ids, context=context) diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml index bd01e50baa5..47106155bfd 100644 --- a/addons/mail/mail_group_view.xml +++ b/addons/mail/mail_group_view.xml @@ -60,7 +60,14 @@
-

+
+

+
+ +
+
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 1655ab98afb..fe8881d4de9 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -79,7 +79,7 @@ class mail_thread(osv.Model): return [('id', 'in', msg_ids)] _columns = { - 'message_ids': fields.function(_get_message_ids, method=True, + 'message_ids': fields.function(_get_message_ids, fnct_search=_search_message_ids, type='one2many', obj='mail.message', _fields_id = 'res_id', string='Temp messages', multi="_get_message_ids", @@ -560,6 +560,56 @@ class mail_thread(osv.Model): msgs = msg_obj.read(cr, uid, msg_ids, context=context) return msgs + + def _get_user(self, cr, uid, alias, context): + """ + param alias: browse record of alias. + return: int user_id. + """ + + user_obj = self.pool.get('res.user') + user_id = 1 + if alias.alias_user_id: + user_id = alias.alias_user_id.id + #if user_id not defined in the alias then search related user using name of Email sender + else: + from_email = msg.get('from') + user_ids = user_obj.search(cr, uid, [('name','=',from_email)], context) + if user_ids: + user_id = user_obj.browse(cr, uid, user_ids[0], context).id + return user_id + + def message_catchall(self, cr, uid, message, context=None): + """ + Process incoming mail and call messsage_process using details of the mail.alias model + else raise Exception so that mailgate script will reject the mail and + send notification mail sender that this mailbox does not exist so your mail have been rejected. + """ + mail_alias = self.pool.get('mail.alias') + mail_message = self.pool.get('mail.message') + if isinstance(message, xmlrpclib.Binary): + message = str(message.data) + if isinstance(message, unicode): + message = message.encode('utf-8') + msg_txt = email.message_from_string(message) + msg = mail_message.parse_message(msg_txt) + alias_name = msg.get('to').split("@")[0] # @@@@ + alias_ids = mail_alias.search(cr, uid, [('alias_name','=',alias_name)]) + #if alias found then call message_process method. # @@@@ + if alias_ids: + alias_id = mail_alias.browse(cr, uid, alias_ids[0], context) + user_id = self._get_user( cr, uid, alias_id, context) + alias_defaults = dict(eval(alias_id.alias_defaults or {})) + self.message_process(cr, user_id, alias_id.alias_model_id.model, message, + custom_values=alias_defaults, + thread_id=alias_id.alias_force_thread_id or False, + context=context) + else: + #if Mail box for the intended Mail Alias then give logger warning + _logger.warning("No Mail Alias Found for the name '%s'."%(alias_name)) + raise # @@@@ + return True + #------------------------------------------------------ # Mail gateway #------------------------------------------------------ @@ -567,7 +617,7 @@ class mail_thread(osv.Model): def message_process(self, cr, uid, model, message, custom_values=None, save_original=False, strip_attachments=False, - context=None): + thread_id=None, context=None): """Process an incoming RFC2822 email message related to the given thread model, relying on ``mail.message.parse()`` for the parsing operation, and then calling ``message_new`` @@ -587,6 +637,10 @@ class mail_thread(osv.Model): email source attached to the message after it is imported. :param bool strip_attachments: whether to strip all attachments before processing the message, in order to save some space. + :param int thread_id: optional ID of the record/thread from ``model`` + to which this mail should be attached. When provided, this + overrides the automatic detection based on the message + headers. """ # extract message bytes - we are forced to pass the message as binary because # we don't know its encoding until we parse its headers and hence can't @@ -594,12 +648,12 @@ class mail_thread(osv.Model): if isinstance(message, xmlrpclib.Binary): message = str(message.data) - model_pool = self.pool.get(model) - if self._name != model: - if context is None: context = {} - context.update({'thread_model': model}) + if context is None: context = {} mail_message = self.pool.get('mail.message') + model_pool = self.pool.get(model) + if self._name != model: + context.update({'thread_model': model}) # Parse Message # Warning: message_from_string doesn't always work correctly on unicode, @@ -621,8 +675,7 @@ class mail_thread(osv.Model): return model_pool.message_new(cr, uid, msg, custom_values, context=context) - res_id = False - if msg.get('references') or msg.get('in-reply-to'): + if not thread_id and (msg.get('references') or msg.get('in-reply-to')): references = msg.get('references') or msg.get('in-reply-to') if '\r\n' in references: references = references.split('\r\n') @@ -630,28 +683,25 @@ class mail_thread(osv.Model): references = references.split(' ') for ref in references: ref = ref.strip() - res_id = tools.reference_re.search(ref) - if res_id: - res_id = res_id.group(1) - else: - res_id = tools.res_re.search(msg['subject']) - if res_id: - res_id = res_id.group(1) - if res_id: - res_id = res_id - if model_pool.exists(cr, uid, res_id): - if hasattr(model_pool, 'message_update'): - model_pool.message_update(cr, uid, [res_id], msg, {}, context=context) - else: - # referenced thread was not found, we'll have to create a new one - res_id = False - if not res_id: - res_id = create_record(msg) + thread_id = tools.reference_re.search(ref) + if not thread_id: + thread_id = tools.res_re.search(msg['subject']) + if thread_id: + thread_id = int(thread_id.group(1)) + if not model_pool.exists(cr, uid, thread_id) or \ + not hasattr(model_pool, 'message_update'): + # referenced thread not found or not updatable, + # -> create a new one + thread_id = False + if not thread_id: + thread_id = create_record(msg) + else: + model_pool.message_update(cr, uid, [thread_id], msg, {}, context=context) # To forward the email to other followers - self.message_forward(cr, uid, model, [res_id], msg_txt, context=context) + self.message_forward(cr, uid, model, [thread_id], msg_txt, context=context) # Set as Unread - model_pool.message_mark_as_unread(cr, uid, [res_id], context=context) - return res_id + model_pool.message_mark_as_unread(cr, uid, [thread_id], context=context) + return thread_id def message_new(self, cr, uid, msg_dict, custom_values=None, context=None): """Called by ``message_process`` when a new message is received @@ -693,23 +743,18 @@ class mail_thread(osv.Model): return res_id def message_update(self, cr, uid, ids, msg_dict, update_vals=None, context=None): - """ Called by ``message_process`` when a new message is received - for an existing thread. The default behavior is to create a - new mail.message in the given thread (by calling - ``message_append_dict``) - Additional behavior may be implemented by overriding this - method. - - :param dict msg_dict: a map containing the email details and - attachments. See ``message_process`` and - ``mail.message.parse()`` for details. - :param dict vals: a dict containing values to update records + """Called by ``message_process`` when a new message is received + for an existing thread. The default behavior is to create a + new mail.message in the given thread (by calling + ``message_append_dict``) + Additional behavior may be implemented by overriding this + method. + :param dict msg_dict: a map containing the email details and + attachments. See ``message_process`` and + ``mail.message.parse()`` for details. + :param dict update_vals: a dict containing values to update records given their ids; if the dict is None or is void, no write operation is performed. - :param dict context: if a ``thread_model`` value is present - in the context, its value will be used - to determine the model of the thread to - update (instead of the current model). """ if update_vals: self.write(cr, uid, ids, update_vals, context=context) diff --git a/addons/mail/res_config.py b/addons/mail/res_config.py new file mode 100644 index 00000000000..ce2f75687ba --- /dev/null +++ b/addons/mail/res_config.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-Today OpenERP SA () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +############################################################################## + +from openerp.osv import osv, fields + +class project_configuration(osv.TransientModel): + _inherit = 'base.config.settings' + + _columns = { + 'alias_domain' : fields.char('Alias Domain', + help="If you have setup a catch-all email domain redirected to " + "the OpenERP server, enter the domain name here."), + } + + def get_default_alias_domain(self, cr, uid, ids, context=None): + return {'alias_domain': self.pool.get("ir.config_parameter").get_param(cr, uid, "mail.catchall.domain", context=context)} + + def set_alias_domain(self, cr, uid, ids, context=None): + config_parameters = self.pool.get("ir.config_parameter") + for record in self.browse(cr, uid, ids, context=context): + config_parameters.set_param(cr, uid, "mail.catchall.domain", record.alias_domain or '', context=context) \ No newline at end of file diff --git a/addons/mail/res_config_view.xml b/addons/mail/res_config_view.xml new file mode 100644 index 00000000000..a17a1f22a80 --- /dev/null +++ b/addons/mail/res_config_view.xml @@ -0,0 +1,18 @@ + + + + + base.config.settings.mail.alias + base.config.settings + + form + + +
+
+
+
+
+
+
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py index 89767473754..f0b987b369f 100644 --- a/addons/mail/res_users.py +++ b/addons/mail/res_users.py @@ -19,9 +19,15 @@ # ############################################################################## +import logging + from osv import osv, fields +from openerp.modules.registry import RegistryManager +from openerp import SUPERUSER_ID from tools.translate import _ +_logger = logging.getLogger(__name__) + class res_users(osv.osv): """ Update of res.users class - add a preference about sending emails about notifications @@ -30,6 +36,7 @@ class res_users(osv.osv): """ _name = 'res.users' _inherit = ['res.users', 'mail.thread'] + _inherits = {'mail.alias': 'alias_id'} _columns = { 'notification_email_pref': fields.selection([ @@ -40,12 +47,15 @@ class res_users(osv.osv): ], 'Receive Feeds by Email', required=True, help="Choose in which case you want to receive an email when you "\ "receive new feeds."), + 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True, + help="Email address internally associated with this user. Incoming emails will appear " + "in the user's notifications."), } _defaults = { 'notification_email_pref': 'to_me', } - + def __init__(self, pool, cr): """ Override of __init__ to add access rights on notification_email_pref field. Access rights are disabled by default, but allowed on @@ -56,18 +66,58 @@ class res_users(osv.osv): self.SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS) self.SELF_WRITEABLE_FIELDS.append('notification_email_pref') return init_res + + def init(self, cr): + # Installation hook to create aliases for all users, right after _auto_init + registry = RegistryManager.get(cr.dbname) + mail_alias = registry.get('mail.alias') + res_users = registry.get('res.users') + users_no_alias = res_users.search(cr, SUPERUSER_ID, [('alias_id', '=', False)]) + # Use read() not browse(), to avoid prefetching uninitialized inherited fields + for user_data in res_users.read(cr, SUPERUSER_ID, users_no_alias, ['login']): + alias_id = mail_alias.create_unique_alias(cr, SUPERUSER_ID, {'alias_name': user_data['login'], + 'alias_force_id': user_data['id']}, + model_name=self._name) + res_users.write(cr, SUPERUSER_ID, user_data['id'], {'alias_id': alias_id}) + _logger.info('Mail alias created for user %s (uid %s)', user_data['login'], user_data['id']) + + # Finally attempt to reinstate the missing constraint + try: + cr.execute('ALTER TABLE res_users ALTER COLUMN alias_id SET NOT NULL') + except Exception: + pass + def create(self, cr, uid, data, context=None): + # create default alias same as the login + mail_alias = self.pool.get('mail.alias') + alias_id = mail_alias.create_unique_alias(cr, uid, {'alias_name': data['login']}, model_name=self._name, context=context) + data['alias_id'] = alias_id user_id = super(res_users, self).create(cr, uid, data, context=context) - user = self.browse(cr, uid, [user_id], context=context)[0] + mail_alias.write(cr, SUPERUSER_ID, [alias_id], {"alias_force_thread_id": user_id}, context) + + user = self.browse(cr, uid, user_id, context=context) # make user follow itself self.message_subscribe(cr, uid, [user_id], [user_id], context=context) # create a welcome message - company_name = user.company_id.name if user.company_id else 'the company' - message = _('%s has joined %s! Welcome in OpenERP !') % (user.name, company_name) - self.message_append_note(cr, uid, [user_id], subject='Welcom to OpenERP', body=message, type='comment', context=context) + company_name = user.company_id.name if user.company_id else _('the company') + message = _('%s has joined %s! Welcome to OpenERP !') % (user.name, company_name) + self.message_append_note(cr, uid, [user_id], subject='Welcome to OpenERP', body=message, type='comment', context=context) return user_id + + def write(self, cr, uid, ids, vals, context=None): + # User alias is sync'ed with login + if vals.get('login'): vals['alias_name'] = vals['login'] + return super(res_users, self).write(cr, uid, ids, vals, context=context) + def unlink(self, cr, uid, ids, context=None): + # Cascade-delete mail aliases as well, as they should not exist without the user. + alias_pool = self.pool.get('mail.alias') + alias_ids = [user.alias_id.id for user in self.browse(cr, uid, ids, context=context) if user.alias_id] + res = super(res_users, self).unlink(cr, uid, ids, context=context) + alias_pool.unlink(cr, uid, alias_ids, context=context) + return res + def message_search_get_domain(self, cr, uid, ids, context=None): """ Override of message_search_get_domain for partner discussion page. The purpose is to add messages directly sent to user using diff --git a/addons/mail/res_users_view.xml b/addons/mail/res_users_view.xml index e268997ac2c..d739e56661e 100644 --- a/addons/mail/res_users_view.xml +++ b/addons/mail/res_users_view.xml @@ -31,6 +31,10 @@ + + + + diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 1fb909f4435..3677d424c14 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -5,3 +5,5 @@ access_mail_thread,mail.thread,model_mail_thread,base.group_user,1,1,1,0 access_mail_subscription_all,mail.subscription.all,model_mail_subscription,,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_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 diff --git a/addons/mail/static/scripts/openerp_mailgate.py b/addons/mail/static/scripts/openerp_mailgate.py index 68ae20d35e3..64dafc56785 100755 --- a/addons/mail/static/scripts/openerp_mailgate.py +++ b/addons/mail/static/scripts/openerp_mailgate.py @@ -94,22 +94,23 @@ class RPCProxy(object): return self.rpc.execute(self.dbname, self.user_id, self.passwd, *request, **kwargs) class EmailParser(object): - def __init__(self, uid, password, model, email_default, dbname, host, port): + def __init__(self, uid, password, dbname, host, port, model=False, email_default=False): self.rpc = RPCProxy(uid, password, host=host, port=port, dbname=dbname) - try: - self.model_id = int(model) - self.model = str(model) - except: - self.model_id = self.rpc('ir.model', 'search', [('model', '=', model)])[0] - self.model = str(model) - self.email_default = email_default + if model: + try: + self.model_id = int(model) + self.model = str(model) + except: + self.model_id = self.rpc('ir.model', 'search', [('model', '=', model)])[0] + self.model = str(model) + self.email_default = email_default - def parse(self, message, custom_values=None, save_original=None): + def parse(self, method, message, custom_values=None, save_original=None): # pass message as bytes because we don't know its encoding until we parse its headers # and hence can't convert it to utf-8 for transport res_id = self.rpc('mail.thread', - 'message_process', + method, self.model, xmlrpclib.Binary(message), custom_values or {}, @@ -156,30 +157,29 @@ def main(): """ Receive the email via the stdin and send it to the OpenERP Server """ + parser = configure_parser() (options, args) = parser.parse_args() - - + method = "message_process" email_parser = EmailParser(options.userid, options.password, - options.model, - options.default, - dbname=options.dbname, - host=options.host, - port=options.port) - - + options.dbname, + options.host, + options.port, + model=options.model, + email_default= options.default) msg_txt = sys.stdin.read() - custom_values = {} + if not options.model: + method = "message_catchall" try: - custom_values = dict(eval(options.custom_values or {} )) + custom_values = dict(eval(options.custom_values or "{}" )) except: import traceback traceback.print_exc() try: - email_parser.parse(msg_txt, custom_values, options.save_original or False) + email_parser.parse(method, msg_txt, custom_values, options.save_original or False) except Exception: msg = '\n'.join([ 'parameters', diff --git a/addons/mrp_repair/mrp_repair.py b/addons/mrp_repair/mrp_repair.py index fc898dbf419..c9b31dcc5cc 100644 --- a/addons/mrp_repair/mrp_repair.py +++ b/addons/mrp_repair/mrp_repair.py @@ -28,6 +28,7 @@ import decimal_precision as dp class mrp_repair(osv.osv): _name = 'mrp.repair' + _inherit = 'mail.thread' _description = 'Repair Order' def _amount_untaxed(self, cr, uid, ids, field_name, arg, context=None): @@ -119,16 +120,16 @@ class mrp_repair(osv.osv): 'partner_id' : fields.many2one('res.partner', 'Partner', select=True, help='Choose partner for whom the order will be invoiced and delivered.'), 'address_id': fields.many2one('res.partner', 'Delivery Address', domain="[('parent_id','=',partner_id)]"), 'default_address_id': fields.function(_get_default_address, type="many2one", relation="res.partner"), - 'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number', select=True, domain="[('product_id','=',product_id)]"), + 'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number', select=True, states={'draft':[('readonly',False)]},domain="[('product_id','=',product_id)]"), 'state': fields.selection([ ('draft','Quotation'), - ('cancel','Cancel'), + ('cancel','Cancelled'), ('confirmed','Confirmed'), ('under_repair','Under Repair'), ('ready','Ready to Repair'), ('2binvoiced','To be Invoiced'), ('invoice_except','Invoice Exception'), - ('done','Done') + ('done','Repaired') ], 'Status', readonly=True, help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed repair order. \ \n* The \'Confirmed\' state is used when a user confirms the repair order. \ @@ -219,7 +220,7 @@ class mrp_repair(osv.osv): @return: Dictionary of values. """ data = {} - data['value'] = {} + data['value'] = {'guarantee_limit': False, 'location_id': False, 'prodlot_id': False, 'partner_id': False} if not prod_id: return data if move_id: @@ -229,6 +230,7 @@ class mrp_repair(osv.osv): data['value']['guarantee_limit'] = limit.strftime('%Y-%m-%d') data['value']['location_id'] = move.location_dest_id.id data['value']['location_dest_id'] = move.location_dest_id.id + data['value']['prodlot_id'] = move.prodlot_id.id if move.partner_id: data['value']['partner_id'] = move.partner_id.id else: @@ -329,12 +331,11 @@ class mrp_repair(osv.osv): self.write(cr, uid, [o.id], {'state': '2binvoiced'}) else: self.write(cr, uid, [o.id], {'state': 'confirmed'}) - if not o.operations: - raise osv.except_osv(_('Error !'),_('You cannot confirm a repair order which has no line.')) for line in o.operations: if line.product_id.track_production and not line.prodlot_id: raise osv.except_osv(_('Warning'), _("Serial number is required for operation line with product '%s'") % (line.product_id.name)) mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'}) + self.set_confirm_send_note(cr, uid, ids) return True def action_cancel(self, cr, uid, ids, context=None): @@ -343,12 +344,18 @@ class mrp_repair(osv.osv): """ mrp_line_obj = self.pool.get('mrp.repair.line') for repair in self.browse(cr, uid, ids, context=context): - mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'}, context=context) + if not repair.invoiced: + mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'}, context=context) + else: + raise osv.except_osv(_('Warning!'),_('Repair order is already invoiced.')) self.write(cr,uid,ids,{'state':'cancel'}) + self.set_cancel_send_note(cr, uid, ids, context) return True def wkf_invoice_create(self, cr, uid, ids, *args): - return self.action_invoice_create(cr, uid, ids) + self.action_invoice_create(cr, uid, ids) + self.set_toinvoiced_send_note(cr, uid, ids) + return True def action_invoice_create(self, cr, uid, ids, group=False, context=None): """ Creates invoice(s) for repair order. @@ -463,6 +470,7 @@ class mrp_repair(osv.osv): self.pool.get('mrp.repair.line').write(cr, uid, [l.id for l in repair.operations], {'state': 'confirmed'}, context=context) self.write(cr, uid, [repair.id], {'state': 'ready'}) + self.set_ready_send_note(cr, uid, ids, context) return True def action_repair_start(self, cr, uid, ids, context=None): @@ -474,6 +482,7 @@ class mrp_repair(osv.osv): repair_line.write(cr, uid, [l.id for l in repair.operations], {'state': 'confirmed'}, context=context) repair.write({'state': 'under_repair'}) + self.set_start_send_note(cr, uid, ids, context) return True def action_repair_end(self, cr, uid, ids, context=None): @@ -538,7 +547,6 @@ class mrp_repair(osv.osv): 'name': repair.name, 'picking_id': picking, 'product_id': repair.product_id.id, - 'product_qty': move.product_uom_qty or 1.0, 'product_uom': repair.product_id.uom_id.id, 'prodlot_id': repair.prodlot_id and repair.prodlot_id.id or False, 'partner_id': repair.address_id and repair.address_id.id or False, @@ -552,8 +560,52 @@ class mrp_repair(osv.osv): res[repair.id] = picking else: self.write(cr, uid, [repair.id], {'state': 'done'}) + self.set_done_send_note(cr, uid, [repair.id], context) return res + + def create(self, cr, uid, vals, context=None): + repair_id = super(mrp_repair, self).create(cr, uid, vals, context=context) + self.create_send_note(cr, uid, [repair_id], context=context) + return repair_id + + def create_send_note(self, cr, uid, ids, context=None): + for repair in self.browse(cr, uid, ids, context): + message = _("Repair Order for %s has been created." % (repair.product_id.name)) + self.message_append_note(cr, uid, [repair.id], body=message, context=context) + return True + + def set_start_send_note(self, cr, uid, ids, context=None): + for repair in self.browse(cr, uid, ids, context): + message = _("Repair Order for %s has been started." % (repair.product_id.name)) + self.message_append_note(cr, uid, [repair.id], body=message, context=context) + return True + + def set_toinvoiced_send_note(self, cr, uid, ids, context=None): + for repair in self.browse(cr, uid, ids, context): + message = _("Draft Invoice of %s %s waiting for validation.") % (repair.invoice_id.amount_total, repair.invoice_id.currency_id.symbol) + self.message_append_note(cr, uid, [repair.id], body=message, context=context) + return True + + def set_confirm_send_note(self, cr, uid, ids, context=None): + for repair in self.browse(cr, uid, ids, context): + message = _( "Repair Order for %s has been accepted." % (repair.product_id.name)) + self.message_append_note(cr, uid, [repair.id], body=message, context=context) + return True + + def set_cancel_send_note(self, cr, uid, ids, context=None): + message = _("Repair has been cancelled.") + self.message_append_note(cr, uid, ids, body=message, context=context) + return True + + def set_ready_send_note(self, cr, uid, ids, context=None): + message = _("Repair Order is now ready to repair.") + self.message_append_note(cr, uid, ids, body=message, context=context) + return True + def set_done_send_note(self, cr, uid, ids, context=None): + message = _("Repair Order is closed.") + self.message_append_note(cr, uid, ids, body=message, context=context) + return True mrp_repair() @@ -643,7 +695,7 @@ class mrp_repair_line(osv.osv, ProductChangeMixin): 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price')), 'price_subtotal': fields.function(_amount_line, string='Subtotal',digits_compute= dp.get_precision('Sale Price')), 'tax_id': fields.many2many('account.tax', 'repair_operation_line_tax', 'repair_operation_line_id', 'tax_id', 'Taxes'), - 'product_uom_qty': fields.float('Quantity (Unit of Measure)', digits=(16,2), required=True), + 'product_uom_qty': fields.float('Quantity', digits_compute= dp.get_precision('Product UoS'), required=True), 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True), 'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number',domain="[('product_id','=',product_id)]"), 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True), @@ -654,7 +706,7 @@ class mrp_repair_line(osv.osv, ProductChangeMixin): ('draft','Draft'), ('confirmed','Confirmed'), ('done','Done'), - ('cancel','Canceled')], 'Status', required=True, readonly=True, + ('cancel','Cancelled')], 'Status', required=True, readonly=True, help=' * The \'Draft\' state is set automatically as draft when repair order in draft state. \ \n* The \'Confirmed\' state is set automatically as confirm when repair order in confirm state. \ \n* The \'Done\' state is set automatically when repair order is completed.\ diff --git a/addons/mrp_repair/mrp_repair_view.xml b/addons/mrp_repair/mrp_repair_view.xml index 74329f6b7fe..6c47a5c002f 100644 --- a/addons/mrp_repair/mrp_repair_view.xml +++ b/addons/mrp_repair/mrp_repair_view.xml @@ -13,8 +13,8 @@ - - + + @@ -36,32 +36,33 @@