[MERGE]merge with parent branch

bzr revid: sgo@tinyerp.com-20120904093250-fwawd23g26064wnl
This commit is contained in:
Sanjay Gohel (Open ERP)
2012-09-04 15:02:50 +05:30
75 changed files with 5025 additions and 737 deletions
+7 -7
View File
@@ -30,6 +30,8 @@ from osv import fields, osv
import decimal_precision as dp
from tools.translate import _
from tools.float_utils import float_round
from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
@@ -819,7 +821,7 @@ class account_journal(osv.osv):
if not 'sequence_id' in vals or not vals['sequence_id']:
# if we have the right to create a journal, we should be able to
# create it's sequence.
vals.update({'sequence_id': self.create_sequence(cr, 1, vals, context)})
vals.update({'sequence_id': self.create_sequence(cr, SUPERUSER_ID, vals, context)})
return super(account_journal, self).create(cr, uid, vals, context)
def name_get(self, cr, user, ids, context=None):
@@ -1858,7 +1860,7 @@ class account_tax(osv.osv):
def get_precision_tax():
def change_digit_tax(cr):
res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, 'Account')
res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, SUPERUSER_ID, 'Account')
return (16, res+2)
return change_digit_tax
@@ -3347,13 +3349,11 @@ class wizard_multi_charts_accounts(osv.osv_memory):
# Install all the templates objects and generate the real objects
acc_template_ref, taxes_ref, tax_code_ref = self._install_template(cr, uid, obj_wizard.chart_template_id.id, company_id, code_digits=obj_wizard.code_digits, obj_wizard=obj_wizard, context=context)
# write values of default taxes for product
# write values of default taxes for product as super user
if obj_wizard.sale_tax and taxes_ref:
ir_values_obj.set(cr, uid, key='default', key2=False, name="taxes_id", company=company_id,
models =[('product.product',False)], value=[taxes_ref[obj_wizard.sale_tax.id]])
ir_values_obj.set_default(cr, SUPERUSER_ID, 'product.product', "taxes_id", [taxes_ref[obj_wizard.sale_tax.id]], for_all_users=True, company_id=company_id)
if obj_wizard.purchase_tax and taxes_ref:
ir_values_obj.set(cr, uid, key='default', key2=False, name="supplier_taxes_id", company=company_id,
models =[('product.product',False)], value=[taxes_ref[obj_wizard.purchase_tax.id]])
ir_values_obj.set_default(cr, SUPERUSER_ID, 'product.product', "supplier_taxes_id", [taxes_ref[obj_wizard.purchase_tax.id]], for_all_users=True, company_id=company_id)
# Create Bank journals
self._create_bank_journals_from_o2m(cr, uid, obj_wizard, company_id, acc_template_ref, context=context)
+4 -5
View File
@@ -1298,16 +1298,15 @@ class account_invoice(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>created</b>.") % (_(self._get_document_type(obj.type))), context=context)
self.message_post(cr, uid, [obj.id],body=_("%s <b>created</b>.") % (self._get_document_type(obj.type)), subtype="new", context=context)
def confirm_paid_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>paid</b>.") % (_(self._get_document_type(obj.type))), context=context)
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>paid</b>.") % (self._get_document_type(obj.type)), subtype="paid", context=context)
def invoice_cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (_(self._get_document_type(obj.type))), context=context)
self.message_post(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (self._get_document_type(obj.type)), subtype="cancelled", context=context)
account_invoice()
class account_invoice_line(osv.osv):
+23 -6
View File
@@ -518,7 +518,6 @@
<!--
Account Statement Sequences
-->
<record id="sequence_reconcile" model="ir.sequence.type">
<field name="name">Account Reconcile</field>
<field name="code">account.reconcile</field>
@@ -554,8 +553,6 @@
<field eval="1" name="number_next"/>
<field eval="1" name="number_increment"/>
</record>
<!--
Invoice requests (deprecated)
-->
@@ -563,8 +560,28 @@
<field name="name">Invoice</field>
<field name="object">account.invoice</field>
</record>
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="name">new</field>
<field name="model_ids" eval="[(4,ref('analytic.model_account_analytic_account')),(4,ref('account.model_account_invoice'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_paid" model="mail.message.subtype">
<field name="name">paid</field>
<field name="model_ids" eval="[(4,ref('account.model_account_invoice'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('account.model_account_invoice'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account.model_account_invoice')), (4,ref('analytic.model_account_analytic_account'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account.model_account_invoice')), (4,ref('analytic.model_account_analytic_account'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account.model_account_invoice')), (4,ref('analytic.model_account_analytic_account'))]"/>
</record>
</data>
</openerp>
+546
View File
@@ -0,0 +1,546 @@
# Norwegian Bokmal 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 <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 00:35+0000\n"
"PO-Revision-Date: 2012-09-02 09:46+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Norwegian Bokmal <nb@li.org>\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-03 05:21+0000\n"
"X-Generator: Launchpad (build 15890)\n"
#. module: account_analytic_plans
#: view:analytic.plan.create.model:0
msgid ""
"This distribution model has been saved.You will be able to reuse it later."
msgstr ""
"Denne fordeling modellen har vært lagret. Du vil kunne bruke den senere."
#. module: account_analytic_plans
#: field:account.analytic.plan.instance.line,plan_id:0
msgid "Plan Id"
msgstr "Plan ID"
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "From Date"
msgstr "Fra dato"
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
#: view:account.crossovered.analytic:0
#: model:ir.actions.act_window,name:account_analytic_plans.action_account_crossovered_analytic
#: model:ir.actions.report.xml,name:account_analytic_plans.account_analytic_account_crossovered_analytic
msgid "Crossovered Analytic"
msgstr "Krysset Analyse"
#. module: account_analytic_plans
#: view:account.analytic.plan:0
#: field:account.analytic.plan,name:0
#: field:account.analytic.plan.line,plan_id:0
#: model:ir.actions.act_window,name:account_analytic_plans.account_analytic_plan_form_action
#: model:ir.model,name:account_analytic_plans.model_account_analytic_plan
#: model:ir.ui.menu,name:account_analytic_plans.menu_account_analytic_plan_action
msgid "Analytic Plan"
msgstr "Analytisk Plan"
#. module: account_analytic_plans
#: field:account.analytic.plan.instance,journal_id:0
#: view:account.crossovered.analytic:0
#: field:account.crossovered.analytic,journal_ids:0
msgid "Analytic Journal"
msgstr "Analytisk journal"
#. module: account_analytic_plans
#: view:account.analytic.plan.line:0
#: model:ir.model,name:account_analytic_plans.model_account_analytic_plan_line
msgid "Analytic Plan Line"
msgstr "Analytisk plan linje"
#. module: account_analytic_plans
#: code:addons/account_analytic_plans/wizard/account_crossovered_analytic.py:61
#, python-format
msgid "User Error"
msgstr "Brukerfeil"
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_account_analytic_plan_instance
msgid "Analytic Plan Instance"
msgstr "Analytisk Plan forekomst"
#. module: account_analytic_plans
#: view:analytic.plan.create.model:0
msgid "Ok"
msgstr "Ok"
#. module: account_analytic_plans
#: field:account.analytic.plan.instance,plan_id:0
msgid "Model's Plan"
msgstr "Modell Plan"
#. module: account_analytic_plans
#: field:account.analytic.plan.instance,account2_ids:0
msgid "Account2 Id"
msgstr "Konto2 ID"
#. module: account_analytic_plans
#: sql_constraint:account.invoice:0
msgid "Invoice Number must be unique per Company!"
msgstr "Fakturanummer må være unik pr. firma!"
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "Amount"
msgstr "Beløp"
#. module: account_analytic_plans
#: constraint:account.journal:0
msgid ""
"Configuration error! The currency chosen should be shared by the default "
"accounts too."
msgstr ""
"Konfigurasjon feil! Den valgte valutaen bør deles av standard kontoer også."
#. module: account_analytic_plans
#: sql_constraint:account.move.line:0
msgid "Wrong credit or debit value in accounting entry !"
msgstr "Feil kredit eller debet beløp i regnskaps oppføringen !"
#. module: account_analytic_plans
#: field:account.analytic.plan.instance,account6_ids:0
msgid "Account6 Id"
msgstr "Konto6 ID"
#. module: account_analytic_plans
#: model:ir.ui.menu,name:account_analytic_plans.menu_account_analytic_multi_plan_action
msgid "Multi Plans"
msgstr "Multi Planer"
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_account_bank_statement_line
msgid "Bank Statement Line"
msgstr "Kontoutskriftlinje"
#. module: account_analytic_plans
#: model:ir.actions.act_window,name:account_analytic_plans.account_analytic_plan_form_action_installer
msgid "Define your Analytic Plans"
msgstr "Definer dine analytiske planer."
#. module: account_analytic_plans
#: constraint:account.invoice:0
msgid "Invalid BBA Structured Communication !"
msgstr "Ugyldig BBA Strukturert Kommunikasjon!"
#. module: account_analytic_plans
#: constraint:account.bank.statement:0
msgid "The journal and period chosen have to belong to the same company."
msgstr "Tidsskriftet og perioden valgt å tilhøre samme selskap."
#. module: account_analytic_plans
#: constraint:account.move.line:0
msgid ""
"The date of your Journal Entry is not in the defined period! You should "
"change the date or remove this constraint from the journal."
msgstr ""
"Datoen for din bilagsregistrering ikke er i den definerte perioden! Du bør "
"endre datoen eller fjerne denne begrensningen fra tidsskriftet."
#. module: account_analytic_plans
#: sql_constraint:account.journal:0
msgid "The code of the journal must be unique per company !"
msgstr "Journalkoden må være unik pr firma!"
#. module: account_analytic_plans
#: field:account.crossovered.analytic,ref:0
msgid "Analytic Account Reference"
msgstr "Analytisk konto Referanse."
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_sale_order_line
msgid "Sales Order Line"
msgstr "Salgsordrelinje"
#. module: account_analytic_plans
#: code:addons/account_analytic_plans/wizard/analytic_plan_create_model.py:47
#: view:analytic.plan.create.model:0
#, python-format
msgid "Distribution Model Saved"
msgstr "Distribusjon Model Lagret"
#. module: account_analytic_plans
#: model:ir.actions.act_window,name:account_analytic_plans.account_analytic_plan_instance_action
msgid "Analytic Distribution's Models"
msgstr "Analytisk distribusjon modeller"
#. module: account_analytic_plans
#: view:account.crossovered.analytic:0
msgid "Print"
msgstr "Skriv ut"
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
#: field:account.analytic.line,percentage:0
msgid "Percentage"
msgstr "Prosentdel"
#. module: account_analytic_plans
#: code:addons/account_analytic_plans/account_analytic_plans.py:221
#, python-format
msgid "A model having this name and code already exists !"
msgstr ""
#. module: account_analytic_plans
#: code:addons/account_analytic_plans/wizard/analytic_plan_create_model.py:41
#, python-format
msgid "No analytic plan defined !"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.instance.line,rate:0
msgid "Rate (%)"
msgstr ""
#. module: account_analytic_plans
#: view:account.analytic.plan:0
#: field:account.analytic.plan,plan_ids:0
#: field:account.journal,plan_id:0
msgid "Analytic Plans"
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "Perc(%)"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.line,max_required:0
msgid "Maximum Allowed (%)"
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "Printing date"
msgstr ""
#. module: account_analytic_plans
#: view:account.analytic.plan.line:0
msgid "Analytic Plan Lines"
msgstr ""
#. module: account_analytic_plans
#: 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 ""
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_account_invoice_line
msgid "Invoice Line"
msgstr ""
#. module: account_analytic_plans
#: constraint:account.move.line:0
msgid ""
"The selected account of your Journal Entry forces to provide a secondary "
"currency. You should remove the secondary currency on the account or select "
"a multi-currency view on the journal."
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "Currency"
msgstr ""
#. module: account_analytic_plans
#: field:account.crossovered.analytic,date1:0
msgid "Start Date"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.instance,account_ids:0
msgid "Account Id"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.instance,account5_ids:0
msgid "Account5 Id"
msgstr ""
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_account_analytic_plan_instance_line
msgid "Analytic Instance Line"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.line,root_analytic_id:0
msgid "Root Account"
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "To Date"
msgstr ""
#. module: account_analytic_plans
#: code:addons/account_analytic_plans/account_analytic_plans.py:341
#: code:addons/account_analytic_plans/account_analytic_plans.py:485
#, python-format
msgid "You have to define an analytic journal on the '%s' journal!"
msgstr ""
#. module: account_analytic_plans
#: field:account.crossovered.analytic,empty_line:0
msgid "Dont show empty lines"
msgstr ""
#. module: account_analytic_plans
#: model:ir.actions.act_window,name:account_analytic_plans.action_analytic_plan_create_model
msgid "analytic.plan.create.model.action"
msgstr ""
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_account_analytic_line
msgid "Analytic Line"
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "Analytic Account :"
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "Analytic Account Reference:"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.line,name:0
msgid "Plan Name"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan,default_instance_id:0
msgid "Default Entries"
msgstr ""
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_account_move_line
msgid "Journal Items"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.instance,account1_ids:0
msgid "Account1 Id"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.line,min_required:0
msgid "Minimum Allowed (%)"
msgstr ""
#. module: account_analytic_plans
#: help:account.analytic.plan.line,root_analytic_id:0
msgid "Root account of this plan."
msgstr ""
#. module: account_analytic_plans
#: code:addons/account_analytic_plans/account_analytic_plans.py:221
#: code:addons/account_analytic_plans/wizard/analytic_plan_create_model.py:38
#: code:addons/account_analytic_plans/wizard/analytic_plan_create_model.py:41
#, python-format
msgid "Error"
msgstr ""
#. module: account_analytic_plans
#: view:analytic.plan.create.model:0
msgid "Save This Distribution as a Model"
msgstr ""
#. module: account_analytic_plans
#: constraint:account.move.line:0
msgid "You can not create journal items on an account of type view."
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "Quantity"
msgstr ""
#. module: account_analytic_plans
#: code:addons/account_analytic_plans/wizard/analytic_plan_create_model.py:38
#, python-format
msgid "Please put a name and a code before saving the model !"
msgstr ""
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_account_crossovered_analytic
msgid "Print Crossovered Analytic"
msgstr ""
#. module: account_analytic_plans
#: code:addons/account_analytic_plans/account_analytic_plans.py:341
#: code:addons/account_analytic_plans/account_analytic_plans.py:485
#, python-format
msgid "No Analytic Journal !"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.instance.line,analytic_account_id:0
msgid "Analytic Account"
msgstr ""
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_account_bank_statement
msgid "Bank Statement"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.instance,account3_ids:0
msgid "Account3 Id"
msgstr ""
#. module: account_analytic_plans
#: constraint:account.analytic.line:0
msgid "You can not create analytic line on view account."
msgstr ""
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_account_invoice
msgid "Invoice"
msgstr ""
#. module: account_analytic_plans
#: view:account.crossovered.analytic:0
#: view:analytic.plan.create.model:0
msgid "Cancel"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.instance,account4_ids:0
msgid "Account4 Id"
msgstr ""
#. module: account_analytic_plans
#: constraint:account.move.line:0
msgid "Company must be the same for its related account and period."
msgstr ""
#. module: account_analytic_plans
#: view:account.analytic.plan.instance.line:0
msgid "Analytic Distribution Lines"
msgstr ""
#. module: account_analytic_plans
#: code:addons/account_analytic_plans/account_analytic_plans.py:234
#, python-format
msgid "The Total Should be Between %s and %s"
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "at"
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "Account Name"
msgstr ""
#. module: account_analytic_plans
#: view:account.analytic.plan.instance.line:0
msgid "Analytic Distribution Line"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.instance,code:0
msgid "Distribution Code"
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "%"
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "100.00%"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.default,analytics_id:0
#: view:account.analytic.plan.instance:0
#: field:account.analytic.plan.instance,name:0
#: field:account.bank.statement.line,analytics_id:0
#: field:account.invoice.line,analytics_id:0
#: field:account.move.line,analytics_id:0
#: model:ir.model,name:account_analytic_plans.model_account_analytic_default
msgid "Analytic Distribution"
msgstr ""
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_account_journal
msgid "Journal"
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "Code"
msgstr ""
#. module: account_analytic_plans
#: model:ir.model,name:account_analytic_plans.model_analytic_plan_create_model
msgid "analytic.plan.create.model"
msgstr ""
#. module: account_analytic_plans
#: field:account.crossovered.analytic,date2:0
msgid "End Date"
msgstr ""
#. module: account_analytic_plans
#: model:ir.actions.act_window,name:account_analytic_plans.account_analytic_instance_model_open
msgid "Distribution Models"
msgstr ""
#. module: account_analytic_plans
#: model:ir.actions.act_window,help:account_analytic_plans.account_analytic_plan_form_action_installer
msgid ""
"To setup a multiple analytic plans environment, you must define the root "
"analytic accounts for each plan set. Then, you must attach a plan set to "
"your account journals."
msgstr ""
#. module: account_analytic_plans
#: constraint:account.move.line:0
msgid "You can not create journal items on closed account."
msgstr ""
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
msgid "Company"
msgstr ""
#. module: account_analytic_plans
#: field:account.analytic.plan.line,sequence:0
msgid "Sequence"
msgstr ""
#. module: account_analytic_plans
#: sql_constraint:account.journal:0
msgid "The name of the journal must be unique per company !"
msgstr ""
#. module: account_analytic_plans
#: code:addons/account_analytic_plans/account_analytic_plans.py:234
#, python-format
msgid "Value Error"
msgstr ""
+3 -3
View File
@@ -1293,17 +1293,17 @@ class account_voucher(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
message = "%s <b>created</b>." % self._document_type[obj.type or False]
self.message_post(cr, uid, [obj.id], body=message, context=context)
self.message_post(cr, uid, [obj.id], body=message, subtype="new", context=context)
def post_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
self.message_post(cr, uid, [obj.id], body=message, context=context)
self.message_post(cr, uid, [obj.id], body=message, subtype="post", context=context)
def reconcile_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
self.message_post(cr, uid, [obj.id], body=message, context=context)
self.message_post(cr, uid, [obj.id], body=message, subtype="reconcile", context=context)
account_voucher()
@@ -2,16 +2,38 @@
<openerp>
<data noupdate="1">
<!-- notify all employees of module installation -->
<record model="mail.message" id="module_install_notification">
<field name="model">mail.group</field>
<field name="res_id" ref="mail.group_all_employees"/>
<field name="type">notification</field>
<field name="subject">eInvoicing &amp; Payments application installed!</field>
<field name="body">OpenERP's electronic invoicing accelerates the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system.
<function model="mail.group" name="message_post">
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
<value eval="[ref('mail.group_all_employees')]"/>
<value>OpenERP's electronic invoicing allows to ease and fasten the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system.
You can track customer payments easily and automate follow-ups. You get an overview of the discussion with your customers on each invoice for easier traceability.
You can track customer payments easily and automate the reminders. You get an overview of the discussion with your customers on each invoice to ensure a full traceability.
For advanced accounting features, you should install the "Accounting and Finance" module.</field>
If you want to use advanced accounting features, you should install the "Accounting and Finance" module.</value>
<value>Module eInvoicing &amp; Payments has been installed.</value>
</function>
<!-- Message Sub Type -->
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
</record>
<record id="mail.mail_subtype_post" model="mail.message.subtype">
<field name="name">post</field>
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_reconcile" model="mail.message.subtype">
<field name="name">reconcile</field>
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('account_voucher.model_account_voucher'))]"/>
</record>
</data>
</openerp>
+1 -1
View File
@@ -297,7 +297,7 @@ class account_analytic_account(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context)
self.message_post(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), subtype="new", context=context)
account_analytic_account()
+18 -17
View File
@@ -25,6 +25,7 @@ from tools.translate import _
import pooler
import time
import tools
from openerp import SUPERUSER_ID
class audittrail_rule(osv.osv):
"""
@@ -252,10 +253,10 @@ class audittrail_objects_proxy(object_proxy):
pool = pooler.get_pool(cr.dbname)
resource_pool = pool.get(model)
model_pool = pool.get('ir.model')
model_ids = model_pool.search(cr, 1, [('model', '=', model)])
model_ids = model_pool.search(cr, SUPERUSER_ID, [('model', '=', model)])
model_id = model_ids and model_ids[0] or False
assert model_id, _("'%s' Model does not exist..." %(model))
model = model_pool.browse(cr, 1, model_id)
model = model_pool.browse(cr, SUPERUSER_ID, model_id)
# fields to log. currently only used by log on read()
field_list = []
@@ -322,7 +323,7 @@ class audittrail_objects_proxy(object_proxy):
data = {}
resource_pool = pool.get(model.model)
# read all the fields of the given resources in super admin mode
for resource in resource_pool.read(cr, 1, res_ids):
for resource in resource_pool.read(cr, SUPERUSER_ID, res_ids):
values = {}
values_text = {}
resource_id = resource['id']
@@ -332,19 +333,19 @@ class audittrail_objects_proxy(object_proxy):
continue
values[field] = resource[field]
# get the textual value of that field for this record
values_text[field] = self.get_value_text(cr, 1, pool, resource_pool, method, field, resource[field])
values_text[field] = self.get_value_text(cr, SUPERUSER_ID, pool, resource_pool, method, field, resource[field])
field_obj = resource_pool._all_columns.get(field).column
if field_obj._type in ('one2many','many2many'):
# check if an audittrail rule apply in super admin mode
if self.check_rules(cr, 1, field_obj._obj, method):
if self.check_rules(cr, SUPERUSER_ID, field_obj._obj, method):
# check if the model associated to a *2m field exists, in super admin mode
x2m_model_ids = pool.get('ir.model').search(cr, 1, [('model', '=', field_obj._obj)])
x2m_model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', field_obj._obj)])
x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False
assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj))
x2m_model = pool.get('ir.model').browse(cr, 1, x2m_model_id)
x2m_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, x2m_model_id)
#recursive call on x2m fields that need to be checked too
data.update(self.get_data(cr, 1, pool, resource[field], x2m_model, method))
data.update(self.get_data(cr, SUPERUSER_ID, pool, resource[field], x2m_model, method))
data[(model.id, resource_id)] = {'text':values_text, 'value': values}
return data
@@ -389,12 +390,12 @@ class audittrail_objects_proxy(object_proxy):
field_obj = field_definition.column
if field_obj._type in ('one2many','many2many'):
# checking if an audittrail rule apply in super admin mode
if self.check_rules(cr, 1, field_obj._obj, method):
if self.check_rules(cr, SUPERUSER_ID, field_obj._obj, method):
# checking if the model associated to a *2m field exists, in super admin mode
x2m_model_ids = pool.get('ir.model').search(cr, 1, [('model', '=', field_obj._obj)])
x2m_model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', field_obj._obj)])
x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False
assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj))
x2m_model = pool.get('ir.model').browse(cr, 1, x2m_model_id)
x2m_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, x2m_model_id)
# the resource_ids that need to be checked are the sum of both old and previous values (because we
# need to log also creation or deletion in those lists).
x2m_old_values_ids = old_values.get(key, {'value': {}})['value'].get(field_name, [])
@@ -402,7 +403,7 @@ class audittrail_objects_proxy(object_proxy):
# We use list(set(...)) to remove duplicates.
res_ids = list(set(x2m_old_values_ids + x2m_new_values_ids))
for res_id in res_ids:
lines.update(self.prepare_audittrail_log_line(cr, 1, pool, x2m_model, res_id, method, old_values, new_values, field_list))
lines.update(self.prepare_audittrail_log_line(cr, SUPERUSER_ID, pool, x2m_model, res_id, method, old_values, new_values, field_list))
# if the value value is different than the old value: record the change
if key not in old_values or key not in new_values or old_values[key]['value'][field_name] != new_values[key]['value'][field_name]:
data = {
@@ -459,9 +460,9 @@ class audittrail_objects_proxy(object_proxy):
vals.update({'method': 'unlink'})
# create the audittrail log in super admin mode, only if a change has been detected
if lines[(model_id, resource_id)]:
log_id = pool.get('audittrail.log').create(cr, 1, vals)
log_id = pool.get('audittrail.log').create(cr, SUPERUSER_ID, vals)
model = pool.get('ir.model').browse(cr, uid, model_id)
self.create_log_line(cr, 1, log_id, model, lines[(model_id, resource_id)])
self.create_log_line(cr, SUPERUSER_ID, log_id, model, lines[(model_id, resource_id)])
return True
def check_rules(self, cr, uid, model, method):
@@ -475,11 +476,11 @@ class audittrail_objects_proxy(object_proxy):
"""
pool = pooler.get_pool(cr.dbname)
if 'audittrail.rule' in pool.models:
model_ids = pool.get('ir.model').search(cr, 1, [('model', '=', model)])
model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', model)])
model_id = model_ids and model_ids[0] or False
if model_id:
rule_ids = pool.get('audittrail.rule').search(cr, 1, [('object_id', '=', model_id), ('state', '=', 'subscribed')])
for rule in pool.get('audittrail.rule').read(cr, 1, rule_ids, ['user_id','log_read','log_write','log_create','log_unlink','log_action','log_workflow']):
rule_ids = pool.get('audittrail.rule').search(cr, SUPERUSER_ID, [('object_id', '=', model_id), ('state', '=', 'subscribed')])
for rule in pool.get('audittrail.rule').read(cr, SUPERUSER_ID, rule_ids, ['user_id','log_read','log_write','log_create','log_unlink','log_action','log_workflow']):
if len(rule['user_id']) == 0 or uid in rule['user_id']:
if rule.get('log_'+method,0):
return True
+3 -2
View File
@@ -7,6 +7,7 @@ import werkzeug.utils
import openerp.modules.registry
import openerp.addons.web.controllers.main
import openerp.addons.web.common.http as openerpweb
from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
@@ -18,7 +19,7 @@ class OAuthController(openerpweb.Controller):
registry = openerp.modules.registry.RegistryManager.get(dbname)
with registry.cursor() as cr:
providers = registry.get('auth.oauth.provider')
l = providers.read(cr, 1, providers.search(cr, 1, [('enabled','=',True)]))
l = providers.read(cr, SUPERUSER_ID, providers.search(cr, SUPERUSER_ID, [('enabled','=',True)]))
return l
@openerpweb.httprequest
@@ -30,7 +31,7 @@ class OAuthController(openerpweb.Controller):
with registry.cursor() as cr:
try:
u = registry.get('res.users')
credentials = u.auth_oauth(cr, 1, provider, kw)
credentials = u.auth_oauth(cr, SUPERUSER_ID, provider, kw)
cr.commit()
return openerp.addons.web.controllers.main.login_and_redirect(req, *credentials)
except AttributeError:
+2 -1
View File
@@ -6,6 +6,7 @@ import simplejson
import openerp
from openerp.osv import osv, fields
from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
@@ -63,7 +64,7 @@ class res_users(osv.Model):
try:
return super(res_users, self).check_credentials(cr, uid, password)
except openerp.exceptions.AccessDenied:
res = self.search(cr, 1, [('id','=',uid),('oauth_access_token','=',password)])
res = self.search(cr, SUPERUSER_ID, [('id','=',uid),('oauth_access_token','=',password)])
if not res:
raise
+5 -4
View File
@@ -23,6 +23,7 @@ import logging
import os
import tempfile
import urllib
from openerp import SUPERUSER_ID
import werkzeug.urls
import werkzeug.exceptions
@@ -176,7 +177,7 @@ class OpenIDController(openerpweb.Controller):
with registry.cursor() as cr:
Modules = registry.get('ir.module.module')
installed = Modules.search_count(cr, 1, ['&', ('name', '=', 'auth_openid'), ('state', '=', 'installed')]) == 1
installed = Modules.search_count(cr, SUPERUSER_ID, ['&', ('name', '=', 'auth_openid'), ('state', '=', 'installed')]) == 1
if installed:
Users = registry.get('res.users')
@@ -196,13 +197,13 @@ class OpenIDController(openerpweb.Controller):
domain += [('openid_url', '=', openid_url), ('active', '=', True)]
ids = Users.search(cr, 1, domain)
ids = Users.search(cr, SUPERUSER_ID, domain)
assert len(ids) < 2
if ids:
user_id = ids[0]
login = Users.browse(cr, 1, user_id).login
login = Users.browse(cr, SUPERUSER_ID, user_id).login
key = randomString(utils.KEY_LENGTH, '0123456789abcdef')
Users.write(cr, 1, [user_id], {'openid_key': key})
Users.write(cr, SUPERUSER_ID, [user_id], {'openid_key': key})
# TODO fill empty fields with the ones from sreg/ax
cr.commit()
@@ -6,6 +6,7 @@ import urlparse
from openerp.tools import config
from openerp.osv import osv, fields
from openerp import SUPERUSER_ID
TWENTY_FOUR_HOURS = 24 * 60 * 60
@@ -76,9 +77,9 @@ class res_users(osv.osv):
MailMessage.send(cr, uid, [msg_id], context=context)
def send_reset_password_request(self, cr, uid, email, context=None):
ids = self.pool.get('res.users').search(cr, 1, [('user_email', '=', email)], context=context)
ids = self.pool.get('res.users').search(cr, SUPERUSER_ID, [('user_email', '=', email)], context=context)
if ids:
self._auth_reset_password_send_email(cr, 1, email, 'reset_password_email', ids[0], context=context)
self._auth_reset_password_send_email(cr, SUPERUSER_ID, email, 'reset_password_email', ids[0], context=context)
return True
#else:
# _m, company_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'main_company')
@@ -109,7 +110,7 @@ class auth_reset_password(osv.TransientModel):
Users = self.pool.get('res.users')
data = Users._auth_reset_password_check_token(cr, uid, values.get('token', ''))
if data:
Users.write(cr, 1, data['uid'], {'password': pw}, context=context)
Users.write(cr, SUPERUSER_ID, data['uid'], {'password': pw}, context=context)
else:
raise osv.except_osv('Error', 'Invalid token')
+2 -1
View File
@@ -5,6 +5,7 @@ import werkzeug.urls
from openerp.modules.registry import RegistryManager
from openerp.addons.web.controllers.main import login_and_redirect
import openerp.addons.web.common.http as openerpweb
from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
@@ -18,7 +19,7 @@ class OpenIDController(openerpweb.Controller):
with registry.cursor() as cr:
try:
Users = registry.get('res.users')
credentials = Users.auth_signup(cr, 1, name, login, password)
credentials = Users.auth_signup(cr, SUPERUSER_ID, name, login, password)
cr.commit()
return login_and_redirect(req, *credentials)
except AttributeError:
+3 -2
View File
@@ -1,5 +1,6 @@
import openerp
from openerp.osv import osv
from openerp import SUPERUSER_ID
class res_users(osv.Model):
_inherit = 'res.users'
@@ -15,9 +16,9 @@ class res_users(osv.Model):
#
user_template_id = self.pool.get('ir.config_parameter').get_param(cr, uid, 'auth.signup_template_user_id', 0)
if user_template_id:
self.pool.get('res.users').copy(cr, 1, user_template_id, new_user, context=context)
self.pool.get('res.users').copy(cr, SUPERUSER_ID, user_template_id, new_user, context=context)
else:
self.pool.get('res.users').create(cr, 1, new_user, context=context)
self.pool.get('res.users').create(cr, SUPERUSER_ID, new_user, context=context)
def auth_signup(self, cr, uid, name, login, password, context=None):
r = (cr.dbname, login, password)
+26
View File
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Openerp sa (<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/>.
#
##############################################################################
import res_company
import ir_translation
import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+50
View File
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Automated Translations through Gengo API',
'version': '0.1',
'category': 'Tools',
'description': """
Automated Translations through Gengo API
----------------------------------------
This module will install passive scheduler job for automated translations
using the Gengo API. To activate it, you must
1) Configure your Gengo authentication parameters under `Settings > Companies > Gengo Parameters`
2) Launch the wizard under `Settings > Application Terms > Gengo: Manual Request of Translation` and follow the wizard.
This wizard will activate the CRON job and the Scheduler and will start the automatic translation via Gengo Services for all the terms where you requested it.
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['base'],
'init_xml': ['gengo_sync_schedular_data.xml'],
'update_xml': [
'ir_translation.xml',
'res_company_view.xml',
'wizard/base_gengo_translations_view.xml',
],
'demo_xml': [],
'test': [],
'installable': True,
'auto_install': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!--Scheduler sync Receive Request-->
<record id="gengo_sync_receive_request_scheduler" model="ir.cron">
<field name="name" >Gengo Sync Translation (Response)</field>
<field eval="False" name="active"/>
<field name="interval_number">20</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field eval="'base.gengo.translations'" name="model"></field>
<field eval="'_sync_response'" name="function"/>
<field eval="'(20,)'" name="args"/>
</record>
<!--Scheduler Sync Send Request-->
<record id="gengo_sync_send_request_scheduler" model="ir.cron">
<field name="name" >Gengo Sync Translation (Request)</field>
<field eval="False" name="active"/>
<field name="interval_number">20</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field eval="'base.gengo.translations'" name="model"></field>
<field eval="'_sync_request'" name="function"/>
<field eval="'(20,)'" name="args"/>
</record>
</data>
</openerp>
+88
View File
@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 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 osv import fields, osv
from tools.translate import _
LANG_CODE_MAPPING = {
'ar_SA': ('ar', 'Arabic'),
'id_ID': ('id', 'Indonesian'),
'nl_NL': ('nl', 'Dutch'),
'fr_CA': ('fr-ca', 'French (Canada)'),
'pl_PL': ('pl', 'Polish'),
'zh_TW': ('zh-tw', 'Chinese (Traditional)'),
'sv_SE': ('sv', 'Swedish'),
'ko_KR': ('ko', 'Korean'),
'pt_PT': ('pt', 'Portuguese (Europe)'),
'en_US': ('en', 'English'),
'ja_JP': ('ja', 'Japanese'),
'es_ES': ('es', 'Spanish (Spain)'),
'zh_CN': ('zh', 'Chinese (Simplified)'),
'de_DE': ('de', 'German'),
'fr_FR': ('fr', 'French'),
'fr_BE': ('fr', 'French'),
'ru_RU': ('ru', 'Russian'),
'it_IT': ('it', 'Italian'),
'pt_BR': ('pt-br', 'Portuguese (Brazil)')
}
class ir_translation(osv.Model):
_name = "ir.translation"
_inherit = "ir.translation"
_columns = {
'gengo_comment': fields.text("Comments & Activity Linked to Gengo"),
'job_id': fields.char('Gengo Job ID', size=32),
"gengo_translation": fields.selection([('machine', 'Translation By Machine'),
('standard', 'Standard'),
('pro', 'Pro'),
('ultra', 'Ultra')], "Gengo Translation Service Level", help='You can select here the service level you want for an automatic translation using Gengo.'),
}
def _get_all_supported_languages(self, cr, uid, context=None):
flag, gengo = self.pool.get('base.gengo.translations').gengo_authentication(cr, uid, context=context)
if not flag:
raise osv.except_osv(_('Gengo Authentication Error'), gengo)
supported_langs = {}
lang_pair = gengo.getServiceLanguagePairs(lc_src='en')
if lang_pair['opstat'] == 'ok':
for g_lang in lang_pair['response']:
if g_lang['lc_tgt'] not in supported_langs:
supported_langs[g_lang['lc_tgt']] = []
supported_langs[g_lang['lc_tgt']] += [g_lang['tier']]
return supported_langs
def _get_gengo_corresponding_language(cr, lang):
return lang in LANG_CODE_MAPPING and LANG_CODE_MAPPING[lang][0] or lang
def _check_lang_support(self, cr, uid, ids, context=None):
for term in self.browse(cr, uid, ids, context=context):
if term.gengo_translation:
supported_langs = self._get_all_supported_languages(cr, uid, context=context)
if supported_langs:
tier = "nonprofit" if term.gengo_translation == 'machine' else term.gengo_translation
language = self._get_gengo_corresponding_language(term.lang)
if tier not in supported_langs.get(language,[]):
return False
return True
_constraints = [
(_check_lang_support, 'The Gengo translation service selected is not supported for this language.', ['gengo_translation'])
]
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_translation_search" model="ir.ui.view">
<field name="name">Translations</field>
<field name="model">ir.translation</field>
<field name="inherit_id" ref="base.view_translation_search"/>
<field name="arch" type="xml">
<search string="Translations">
<filter string="To Approve In Gengo" domain="[('state','=','inprogress'),('gengo_translation','=',True)]"></filter>
</search>
</field>
</record>
<!-- ir.translation form view -->
<record model="ir.ui.view" id="view_ir_translation_inherit_base_gengo_form">
<field name="name">ir.translation.form.inherit</field>
<field name="inherit_id" ref="base.view_translation_form"/>
<field name="model">ir.translation</field>
<field name="arch" type="xml">
<xpath expr="//form[@string='Translations']/sheet" position="inside">
<group string="Gengo Translation Service" col="4" colspan="4">
<field name="gengo_translation" />
<label string="Note: If the translation state is 'In Progress', it means that the translation has to be approved to be uploaded in this system. You are supposed to do that directly by using your Gengo Account"/>
<field name="gengo_comment" nolabel="1" placeholder="Gengo Comments &amp; Activity..." colspan="4"/>
</group>
</xpath>
</field>
</record>
</data>
</openerp>
+37
View File
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 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 osv import fields, osv
class res_company(osv.Model):
_name = "res.company"
_inherit = "res.company"
_columns = {
"gengo_private_key": fields.text("Gengo Private Key"),
"gengo_public_key": fields.text("Gengo Public Key"),
"gengo_comment": fields.text("Comments", help="This comment will be automatically be enclosed in each an every request sent to Gengo"),
"gengo_auto_approve": fields.boolean("Auto Approve Translation ?", help="Jobs are Automatically Approved by Gengo."),
}
_defaults = {
"gengo_auto_approve": True,
}
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<!-- res.company form view -->
<record model="ir.ui.view" id="view_company_inherit_base_gengo_form">
<field name="name">res.company.form.inherit</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="model">res.company</field>
<field name="arch" type="xml">
<xpath expr="//notebook[last()]" position="inside">
<page string="Gengo Parameters">
<group col="2">
<group col="1" string="Public Key">
<field name="gengo_public_key" nolabel="1" placeholder="Add Gengo login Public Key..."/>
</group>
<group col="1" string="Private Key">
<field name="gengo_private_key" password="True" nolabel="1" placeholder="Add Gengo login Private Key..."/>
</group>
</group>
<group col="4">
<field name="gengo_auto_approve"/>
</group>
<group string="Comments for Translator" col="1">
<field name="gengo_comment" nolabel="1" placeholder="Add your comments here for translator...."/>
</group>
</page>
</xpath>
</field>
</record>
</data>
</openerp>
+24
View File
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base_gengo_translations
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,253 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 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 osv import osv, fields
from tools.translate import _
import re
try:
from mygengo import MyGengo
except ImportError:
raise osv.except_osv(_('Gengo ImportError'), _('Please install mygengo lib from http://pypi.python.org/pypi/mygengo'))
import logging
import tools
import time
_logger = logging.getLogger(__name__)
GENGO_DEFAULT_LIMIT = 20
DEFAULT_CRON_VALS = {
'active': True,
'interval_number': 20,
'interval_type': 'minutes',
'model': "'base.gengo.translations'",
'args': "'(%s,)'" % (str(GENGO_DEFAULT_LIMIT)),
}
class base_gengo_translations(osv.osv_memory):
_name = 'base.gengo.translations'
_columns = {
'restart_send_job': fields.boolean("Restart Sending Job"),
'lang_id': fields.many2one('res.lang', 'Language', help="Leave empty if you don't want to restrict the request to a single language"),
}
def gengo_authentication(self, cr, uid, context=None):
'''
This method tries to open a connection with Gengo. For that, it uses the Public and Private
keys that are linked to the company (given by Gengo on subscription). It returns a tuple with
* as first element: a boolean depicting if the authentication was a success or not
* as second element: the connection, if it was a success, or the error message returned by
Gengo when the connection failed.
This error message can either be displayed in the server logs (if the authentication was called
by the cron) or in a dialog box (if requested by the user), thus it's important to return it
translated.
'''
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
if not user.company_id.gengo_public_key or not user.company_id.gengo_private_key:
return (False, _("Invalid Gengo configuration. Gengo authentication `Public Key` or `Private Key` is missing. Complete Gengo authentication parameters under `Settings > Companies > Gengo Parameters`."))
try:
gengo = MyGengo(
public_key=user.company_id.gengo_public_key.encode('ascii'),
private_key=user.company_id.gengo_private_key.encode('ascii'),
sandbox=True,
)
gengo.getAccountStats()
return (True, gengo)
except Exception, e:
return (False, _("Gengo Connection Error\n%s") %e)
def do_check_schedular(self, cr, uid, xml_id, name, fn, context=None):
"""
This function is used to reset a cron to its default values, or to recreate it if it was deleted.
"""
cron_pool = self.pool.get('ir.cron')
cron_vals = DEFAULT_CRON_VALS.copy()
cron_vals.update({'name': name, "function": fn})
try:
res = []
model, res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base_gengo', xml_id)
cron_pool.write(cr, uid, [res], cron_vals, context=context)
except:
#the cron job was not found, probably deleted previously, so we create it again using default values
cron_vals.update({'numbercall': -1})
return cron_pool.create(cr, uid, cron_vals, context=context)
def act_update(self, cr, uid, ids, context=None):
'''
Function called by the wizard.
'''
if context == None:
context = {}
flag, gengo = self.gengo_authentication(cr, uid, context=context)
if not flag:
raise osv.except_osv(_('Gengo Authentication Error'), gengo)
for wizard in self.browse(cr, uid, ids, context=context):
supported_langs = self.pool.get('ir.translation')._get_all_supported_languages(cr, uid, context=context)
language = self.pool.get('ir.translation')._get_gengo_corresponding_language(wizard.lang_id.code)
if language not in supported_langs:
raise osv.except_osv(_("Warning"), _('This language is not supported by the Gengo translation services.'))
#send immediately a new request for the selected language (if any)
ctx = context.copy()
ctx['gengo_language'] = wizard.lang_id.id
self._sync_request(cr, uid, limit=GENGO_DEFAULT_LIMIT, context=ctx)
self._sync_response( cr, uid, limit=GENGO_DEFAULT_LIMIT, context=ctx)
#check the cron jobs and eventually restart/recreate them
if wizard.restart_send_job:
self.do_check_schedular(cr, uid, 'gengo_sync_send_request_scheduler', _('Gengo Sync Translation (Request)'), '_sync_request', context=context)
self.do_check_schedular(cr, uid, 'gengo_sync_receive_request_scheduler', _('Gengo Sync Translation (Response)'), '_sync_response', context=context)
return {'type': 'ir.actions.act_window_close'}
def _sync_response(self, cr, uid, limit=GENGO_DEFAULT_LIMIT, context=None):
"""
This method will be called by cron services to get translations from
Gengo. It will read translated terms and comments from Gengo and will
update respective ir.translation in openerp.
"""
translation_pool = self.pool.get('ir.translation')
flag, gengo = self.gengo_authentication(cr, uid, context=context)
if not flag:
_logger.warning("%s", gengo)
else:
translation_id = translation_pool.search(cr, uid, [('state', '=', 'inprogress'), ('gengo_translation', 'in', ('machine','standard','pro','ultra'))], limit=limit, context=context)
for term in translation_pool.browse(cr, uid, translation_id, context=context):
up_term = up_comment = 0
if term.job_id:
vals={}
job_response = gengo.getTranslationJob(id=term.job_id)
if job_response['opstat'] != 'ok':
_logger.warning("Invalid Response! Skipping translation Terms with `id` %s." % (term.job_id))
continue
if job_response['response']['job']['status'] == 'approved':
vals.update({'state': 'translated',
'value': job_response['response']['job']['body_tgt']})
up_term += 1
job_comment = gengo.getTranslationJobComments(id=term.job_id)
if job_comment['opstat']=='ok':
gengo_comments=""
for comment in job_comment['response']['thread']:
gengo_comments += _('%s\n\n--\n Commented on %s by %s.') % (comment['body'], time.ctime(comment['ctime']), comment['author'])
vals.update({'gengo_comment': gengo_comments})
up_comment += 1
if vals:
translation_pool.write(cr, uid, term.id, vals)
_logger.info("Successfully Updated `%d` terms and %d Comments." % (up_term, up_comment ))
else:
_logger.warning("%s", 'Cannot retrieve the Gengo job ID for translation %s: %s' % (term.id, term.src))
return True
def _update_terms(self, cr, uid, response, context=None):
"""
Update the terms after their translation were requested to Gengo
"""
translation_pool = self.pool.get('ir.translation')
for jobs in response['jobs']:
for t_id, res in jobs.items():
vals = {}
t_id = int(t_id)
tier = translation_pool.read(cr, uid, [t_id], ['gengo_translation'], context=context)[0]['gengo_translation']
if tier == "machine":
vals.update({'value': res['body_tgt'], 'state': 'translated'})
else:
vals.update({'job_id': res['job_id'], 'state': 'inprogress'})
translation_pool.write(cr, uid, [t_id], vals, context=context)
return
def pack_jobs_request(self, cr, uid, term_ids, context=None):
''' prepare the terms that will be requested to gengo and returns them in a dictionary with following format
{'jobs': {
'term1.id': {...}
'term2.id': {...}
}
}'''
translation_pool = self.pool.get('ir.translation')
jobs = {}
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
auto_approve = 1 if user.company_id.gengo_auto_approve else 0
for term in translation_pool.browse(cr, uid, term_ids, context=context):
if re.search(r"\w", term.src or ""):
jobs[term.id] = {'type': 'text',
'slug': 'single::English to ' + term.lang,
'tier': tools.ustr(term.gengo_translation),
'body_src': term.src,
'lc_src': 'en',
'lc_tgt': translation_pool._get_gengo_corresponding_language(term.lang),
'auto_approve': auto_approve,
'comment': user.company_id.gengo_comment,
}
return {'jobs': jobs}
def _send_translation_terms(self, cr, uid, term_ids, context=None):
"""
Send a request to Gengo with all the term_ids in a different job, get the response and update the terms in
database accordingly.
"""
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
flag, gengo = self.gengo_authentication(cr, uid, context=context)
if flag:
request = self.pack_jobs_request(cr, uid, term_ids, context=context)
if request['jobs']:
result = gengo.postTranslationJobs(jobs=request)
if result['opstat'] == 'ok':
self._update_terms(cr, uid, result['response'], context=context)
else:
_logger.error(gengo)
return True
def _sync_request(self, cr, uid, limit=GENGO_DEFAULT_LIMIT, context=None):
"""
This scheduler will send a job request to the gengo , which terms are
waiing to be translated and for which gengo_translation is enabled.
A special key 'gengo_language' can be passed in the context in order to
request only translations of that language only. Its value is the language
ID in openerp.
"""
if context is None:
context = {}
language_pool = self.pool.get('res.lang')
translation_pool = self.pool.get('ir.translation')
try:
#by default, the request will be made for all terms that needs it, whatever the language
lang_ids = language_pool.search(cr, uid, [], context=context)
if context.get('gengo_language'):
#but if this specific key is given, then we restrict the request on terms of this language only
lang_ids = [context.get('gengo_language')]
langs = [lang.code for lang in language_pool.browse(cr, uid, lang_ids, context=context)]
#search for the n first terms to translate
term_ids = translation_pool.search(cr, uid, [('state', '=', 'to_translate'), ('gengo_translation', 'in', ('machine','standard','pro','ultra')), ('lang', 'in', langs)], limit=limit, context=context)
if term_ids:
self._send_translation_terms(cr, uid, term_ids, context=context)
_logger.info("%s Translation terms have been posted to Gengo successfully", len(term_ids))
else:
_logger.info('No Translation terms to process.')
except Exception, e:
_logger.error("%s", e)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="base_gengo_translation_wizard_from">
<field name="name">base.gengo.translation.form</field>
<field name="model">base.gengo.translations</field>
<field name="arch" type="xml">
<form string="Gengo Request Form" version="7.0">
<group col="4">
<group>
<field name="lang_id" required="1"/>
<field name="restart_send_job"/>
</group>
<group>
<label colspan="2" string="Leave empty for requesting all the terms to Gengo that needs it, regardless of their language"/>
</group>
</group>
<footer>
<button name="act_update" string="Send" type="object" class="oe_highlight"/>
or
<button name="act_cancel" special="cancel" string="Cancel" type="object" class="oe_link"/>
</footer>
</form>
</field>
</record>
<record id="action_wizard_base_gengo_translations" model="ir.actions.act_window">
<field name="name">Gengo: Manual Request of Translation</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">base.gengo.translations</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem action="action_wizard_base_gengo_translations" id="menu_action_wizard_base_gengo_translations" parent="base.menu_translation_app"/>
</data>
</openerp>
-9
View File
@@ -595,18 +595,9 @@ class crm_lead(base_stage, osv.osv):
for lead in self.browse(cr, uid, ids, context=context):
if lead.state in ('done', 'cancel'):
continue
vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context)
self.write(cr, uid, [lead.id], vals, context=context)
self.convert_opportunity_send_note(cr, uid, lead, context=context)
#TOCHECK: why need to change partner details in all messages of lead ?
# TDE FIXME: I propose to delete this, makes no sense to update partner of an already-send email ...
# if lead.partner_id:
# msg_ids = [x.id for x in lead.message_ids]
# mail_message.write(cr, uid, msg_ids, {
# 'partner_id': lead.partner_id.id
# }, context=context)
if user_ids or section_id:
self.allocate_salesman(cr, uid, ids, user_ids, section_id, context=context)
@@ -22,6 +22,7 @@
from osv import osv, fields
import tools
import pooler
from openerp import SUPERUSER_ID
class decimal_precision(osv.osv):
_name = 'decimal.precision'
@@ -56,7 +57,7 @@ decimal_precision()
def get_precision(application):
def change_digit(cr):
res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, application)
res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, SUPERUSER_ID, application)
return (16, res)
return change_digit
+3 -1
View File
@@ -25,6 +25,8 @@ from tools.safe_eval import safe_eval as eval
import time
import urllib
import uuid
from openerp import SUPERUSER_ID
try:
from tools.dict_tools import dict_filter
except ImportError:
@@ -233,7 +235,7 @@ class node_acl_mixin(object):
if props_to_delete:
# explicitly delete, as admin, any of the ids we have identified.
propobj.unlink(cr, 1, props_to_delete)
propobj.unlink(cr, SUPERUSER_ID, props_to_delete)
if lock_data.get('unlock_mode', False):
return lock_found and True
+2 -1
View File
@@ -42,6 +42,7 @@ from tools import config
from xmlrpclib import Transport, ProtocolError
import StringIO
import base64
from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
@@ -361,7 +362,7 @@ class DAVClient(object):
to break if "base_crypt" is used.
"""
ruob = obj.pool.get('res.users')
res = ruob.read(cr, 1, [uid,], ['login', 'password'])
res = ruob.read(cr, SUPERUSER_ID, [uid,], ['login', 'password'])
assert res, "uid %s not found" % uid
self.user = res[0]['login']
self.passwd = res[0]['password']
+42 -81
View File
@@ -29,7 +29,7 @@ from osv import fields
import tools
from tools.translate import _
from tools.html_sanitize import html_sanitize
from tools import html2plaintext
from tools import append_content_to_html
from urllib import quote as quote
_logger = logging.getLogger(__name__)
@@ -40,10 +40,8 @@ except ImportError:
class email_template(osv.osv):
"Templates for sending email"
_inherit = 'mail.mail'
_name = "email.template"
_description = 'Email Templates'
_rec_name = 'name' # override mail.message's behavior
def render_template(self, cr, uid, template, model, res_id, context=None):
"""Render the given template text, replace mako expressions ``${expr}``
@@ -103,57 +101,44 @@ class email_template(osv.osv):
mod_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
return {'value':{'model': mod_name}}
def name_get(self, cr, uid, ids, context=None):
""" Override name_get of mail.message: return directly the template
name, and not the generated name from mail.message.common."""
return [(record.id, record.name) for record in self.browse(cr, uid, ids, context=context)]
_columns = {
'name': fields.char('Name', size=250),
'model_id': fields.many2one('ir.model', 'Related document model'),
'lang': fields.char('Language Selection', size=250,
'name': fields.char('Name'),
'model_id': fields.many2one('ir.model', 'Applies to', help="The kind of document with with this template can be used"),
'model': fields.related('model_id', 'model', type='char', string='Related Document Model',
size=128, select=True, store=True, readonly=True),
'lang': fields.char('Language',
help="Optional translation language (ISO code) to select when sending out an email. "
"If not set, the english version will be used. "
"This should usually be a placeholder expression "
"that provides the appropriate language code, e.g. "
"${object.partner_id.lang.code}."),
"${object.partner_id.lang.code}.",
placeholder="${object.partner_id.lang.code}"),
'user_signature': fields.boolean('Add Signature',
help="If checked, the user's signature will be appended to the text version "
"of the message"),
'report_name': fields.char('Report Filename', size=200, translate=True,
help="Name to use for the generated report file (may contain placeholders)\n"
"The extension can be omitted and will then come from the report type."),
'report_template':fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
'ref_ir_act_window':fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True,
help="Sidebar action to make this template available on records "
"of the related document model"),
'ref_ir_value':fields.many2one('ir.values', 'Sidebar Button', readonly=True,
help="Sidebar button to open the sidebar action"),
'track_campaign_item': fields.boolean('Resource Tracking',
help="Enable this is you wish to include a special tracking marker "
"in outgoing emails so you can identify replies and link "
"them back to the corresponding resource record. "
"This is useful for CRM leads for example"),
# Overridden mail.message.common fields for technical reasons:
'model': fields.related('model_id', 'model', type='char', string='Related Document Model',
size=128, select=True, store=True, readonly=True),
# we need a separate m2m table to avoid ID collisions with the original mail.message entries
#'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
# 'attachment_id', 'Files to attach',
# help="You may attach files to this template, to be added to all "
# "emails created from this template"),
# Overridden mail.message.common fields to make tooltips more appropriate:
'subject': fields.char('Subject', translate=True, help="Subject (placeholders may be used here)",),
'email_from': fields.char('From', size=128, help="Sender address (placeholders may be used here)"),
'email_to': fields.char('To', size=256, help="Comma-separated recipient addresses (placeholders may be used here)"),
'email_cc': fields.char('Cc', size=256, help="Carbon copy recipients (placeholders may be used here)"),
'reply_to': fields.char('Reply-To', size=250, help="Preferred response address (placeholders may be used here)"),
'email_from': fields.char('From', help="Sender address (placeholders may be used here)"),
'email_to': fields.char('To', help="Comma-separated recipient addresses (placeholders may be used here)"),
'email_cc': fields.char('Cc', help="Carbon copy recipients (placeholders may be used here)"),
'reply_to': fields.char('Reply-To', help="Preferred response address (placeholders may be used here)"),
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
help="Optional preferred server for outgoing mails. If not set, the highest "
"priority one will be used."),
'body_html': fields.text('Rich-text Contents', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"),
'body_html': fields.text('Body', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"),
'report_name': fields.char('Report Filename', translate=True,
help="Name to use for the generated report file (may contain placeholders)\n"
"The extension can be omitted and will then come from the report type."),
'report_template': fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
'ref_ir_act_window': fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True,
help="Sidebar action to make this template available on records "
"of the related document model"),
'ref_ir_value': fields.many2one('ir.values', 'Sidebar Button', readonly=True,
help="Sidebar button to open the sidebar action"),
'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
'attachment_id', 'Attachments',
help="You may attach files to this template, to be added to all "
"emails created from this template"),
'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"),
# Fake fields used to implement the placeholder assistant
'model_object_field': fields.many2one('ir.model.fields', string="Field",
@@ -167,12 +152,8 @@ class email_template(osv.osv):
help="When a relationship field is selected as first field, "
"this field lets you select the target field within the "
"destination document model (sub-model)."),
'null_value': fields.char('Null value', help="Optional value to use if the target field is empty", size=128),
'copyvalue': fields.char('Expression', size=256, help="Final placeholder expression, to be copy-pasted in the desired template field."),
}
_defaults = {
'track_campaign_item': True
'null_value': fields.char('Default Value', help="Optional value to use if the target field is empty"),
'copyvalue': fields.char('Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field."),
}
def create_action(self, cr, uid, ids, context=None):
@@ -217,7 +198,7 @@ class email_template(osv.osv):
if template.ref_ir_value:
ir_values_obj = self.pool.get('ir.values')
ir_values_obj.unlink(cr, uid, template.ref_ir_value.id, context)
except:
except Exception:
raise osv.except_osv(_("Warning"), _("Deletion of the action record failed."))
return True
@@ -289,32 +270,14 @@ class email_template(osv.osv):
:param res_id: id of the record to use for rendering the template (model
is taken from template definition)
:returns: a dict containing all relevant fields for creating a new
mail.message entry, with the addition one additional
special key ``attachments`` containing a list of
mail.mail entry, with one extra key ``attachments``, in the
format expected by :py:meth:`mail_thread.message_post`.
"""
if context is None:
context = {}
values = {
'subject': False,
'body_html': False,
'email_from': False,
'email_to': False,
'email_cc': False,
'reply_to': False,
'auto_delete': False,
'model': False,
'res_id': False,
'mail_server_id': False,
'attachments': False,
'attachment_ids': False,
'message_id': False,
'state': 'outgoing',
}
if not template_id:
return values
report_xml_pool = self.pool.get('ir.actions.report.xml')
template = self.get_email_template(cr, uid, template_id, res_id, context)
values = {'model': template.model_id.model}
for field in ['subject', 'body_html', 'email_from',
'email_to', 'email_cc', 'reply_to']:
@@ -322,21 +285,20 @@ class email_template(osv.osv):
template.model, res_id, context=context) \
or False
if values['body_html']:
values['body'] = html_sanitize(values['body_html'])
values['body_text'] = html2plaintext(values['body_html'])
if template.user_signature:
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
values['body'] += '\n\n' + signature
values['body_html'] = append_content_to_html(values['body_html'], signature)
if values['body_html']:
values['body'] = html_sanitize(values['body_html'])
values.update(mail_server_id = template.mail_server_id.id or False,
auto_delete = template.auto_delete,
model=template.model,
res_id=res_id or False)
attachments = {}
# Add report as a Document
attachments = []
# Add report in attachments
if template.report_template:
report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
report_service = 'report.' + report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
@@ -352,12 +314,11 @@ class email_template(osv.osv):
ext = "." + format
if not report_name.endswith(ext):
report_name += ext
attachments[report_name] = result
attachments.append(report_name, result)
# Add document attachments
# Add template attachments
for attach in template.attachment_ids:
# keep the bytes as fetched from the db, base64 encoded
attachments[attach.datas_fname] = attach.datas
attachments.append(attach.datas_fname, attach.datas)
values['attachments'] = attachments
return values
+53 -81
View File
@@ -1,95 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="email_template_form">
<field name="name">email.template.form</field>
<field name="model">email.template</field>
<field name="arch" type="xml">
<form string="Templates" version="7.0">
<sheet>
<group col="4">
<field name="name" required="1"/>
<field name="model_id" required="1" on_change="onchange_model_id(model_id)"/>
<field name="model" invisible="1"/>
</group>
<notebook>
<page string="Email Details">
<group>
<field name="email_from" required="1"/>
<field name="email_to" required="1"/>
<field name="email_cc"/>
<field name="reply_to"/>
<field name="subject" required="1"/>
<notebook colspan="4">
<page string="Body (HTML)">
<field name="body_html" colspan="4" width="250" height="250" nolabel="1"/>
<label string="Note: This is Raw HTML." colspan="4"/>
</page>
</notebook>
<field name="user_signature"/>
<notebook colspan="4">
<page string="Dynamic Values Builder">
<group>
<field name="model_object_field"
domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]"
on_change="onchange_sub_model_object_value_field(model_object_field)"/>
<field name="sub_object" readonly="1"/>
<field name="sub_model_object_field"
domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]"
attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}"
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
<field name="null_value" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
<field name="copyvalue"/>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/><h1><field name="name" required="1"/></h1>
<h3><label for="model_id"/><field name="model_id" required="1" on_change="onchange_model_id(model_id)" class="oe_inline"/></h3>
<field name="model" invisible="1"/>
</div>
<div class="oe_right oe_button_box" name="buttons">
<field name="ref_ir_act_window" invisible="1"/>
<button name="create_action" string="Add context action" type="object"
attrs="{'invisible':[('ref_ir_act_window','!=',False)]}"
help="Display an option on related documents to open a composition wizard with this template"/>
<button name="unlink_action" string="Remove context action" type="object"
attrs="{'invisible':[('ref_ir_act_window','=',False)]}"
help="Remove the contextual action to use this template on related documents"/>
<button name="%(wizard_email_template_preview)d" string="Preview"
type="action" target="new"
context="{'template_id':active_id}"/>
</div>
<notebook>
<page string="Email Details">
<group>
<group string="Addressing">
<field name="email_from" required="1"/>
<field name="email_to" required="1"/>
<field name="email_cc"/>
<field name="reply_to"/>
<field name="user_signature"/>
</group>
<group string="Dynamic Value Builder" class="oe_edit_only">
<field name="model_object_field" domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]" on_change="onchange_sub_model_object_value_field(model_object_field)"/>
<field name="sub_object" readonly="1"/>
<field name="sub_model_object_field" domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]" attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
<field name="null_value" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
<field name="copyvalue"/>
</group>
<group string="Contents" colspan="2">
<field name="subject" required="1"/>
<field name="body_html" width="250" height="450" nolabel="1" colspan="2" placeholder="Email contents (in raw HTML format)"/>
</group>
</page>
</notebook>
<button name="%(wizard_email_template_preview)d" string="Preview Template"
type="action" colspan="4" target="new" icon="gtk-zoom-fit" context="{'template_id':active_id}"/>
</group>
</page>
<page string="Advanced">
<group colspan="2" col="2">
<group colspan="2" col="2">
<separator string="Sidebar Button" colspan="2"/>
<button name="create_action" string="Add sidebar button" type="object" icon="gtk-execute"
colspan="2" attrs="{'invisible':[('ref_ir_act_window','!=',False)]}"
help="Display a button in the sidebar of related documents to open a composition wizard with this template"
/>
<field name="ref_ir_act_window" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"/>
<field name="ref_ir_value" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"/>
<button name="unlink_action" string="Remove sidebar button" type="object" icon="gtk-delete"
colspan="2" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"
help="Remove the sidebar button currently displayed on related documents"
/>
</group>
<group colspan="2" col="2">
<separator string="Advanced Options" colspan="2"/>
<field name="mail_server_id"/>
<field name="track_campaign_item"/>
<field name="message_id"/>
<field name="auto_delete"/>
</page>
<page string="Advanced">
<group>
<field name="lang"/>
<field name="mail_server_id"/>
<field name="auto_delete"/>
<field name="report_template" domain="[('model','=',model)]"/>
<field name="report_name" class="oe_inline"
attrs="{'invisible':[('report_template','=',False)]}"/>
<field name="attachment_ids">
<tree><field name="name"/></tree>
</field>
</group>
</group>
<group colspan="2" col="2">
<separator string="Attachments" colspan="2"/>
<notebook colspan="2">
<page string="Attach Report">
<group>
<field name="report_template" colspan="4"
domain="[('model','=',model)]"/>
<field name="report_name" colspan="4" />
</group>
</page>
<page string="Attach existing files">
<field name="attachment_ids" colspan="4" nolabel="1" height="350"/>
</page>
</notebook>
</group>
</page>
</notebook>
</sheet>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
@@ -99,9 +71,9 @@
<field name="model">email.template</field>
<field name="arch" type="xml">
<tree string="Templates">
<field name="model_id"/>
<field name="mail_server_id" invisible="1"/>
<field name="name"/>
<field name="model_id"/>
<field name="subject"/>
<field name="email_from"/>
<field name="email_to"/>
@@ -1,5 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_email_template,email.template,model_email_template,,1,0,0,0
access_email_template,email.template,model_email_template,,1,1,1,0
access_email_template_system,email.template system,model_email_template,base.group_system,1,1,1,1
access_email_template_manager,email.template,model_email_template,,1,1,1,1
access_email_template_preview_system,email.template.preview system,model_email_template_preview,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_email_template email.template model_email_template 1 0 1 0 1 0
3 access_email_template_system email.template system model_email_template base.group_system 1 1 1 1
access_email_template_manager email.template model_email_template 1 1 1 1
access_email_template_preview_system email.template.preview system model_email_template_preview base.group_system 1 1 1 1
+35 -27
View File
@@ -53,51 +53,59 @@ class test_message_compose(common.TransactionCase):
""" Tests designed for the mail.compose.message wizard updated by email_template. """
cr, uid = self.cr, self.uid
mail_compose = self.registry('mail.compose.message')
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a.a'})
user_admin = self.res_users.browse(cr, uid, uid)
group_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'mail.group')])[0]
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
group_bird_id = self.mail_group.create(cr, uid, {'name': 'Bird', 'description': 'I am angry !'})
group_bird = self.mail_group.browse(cr, uid, group_bird_id)
# Create template on mail.group
group_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'mail.group')])[0]
email_template = self.registry('email.template')
email_template_id = email_template.create(cr, uid, {'model_id': group_model_id,
'name': 'Pigs Template', 'subject': '${record.name}',
'name': 'Pigs Template', 'subject': '${object.name}',
'body_html': '${object.description}', 'user_signature': True,
'email_to': 'b@b c@c', 'email_cc': 'd@d'})
# import pdb
# pdb.set_trace()
'email_to': 'b@b.b c@c.c', 'email_cc': 'd@d.d'})
# Mail data
_subject = 'Pigs'
_body_text = 'Pigs rules'
_subject1 = 'Pigs'
_subject2 = 'Bird'
_body_text1 = 'Pigs rules'
_body_text_html1 = 'Pigs rules<pre>Admin</pre>'
_body_text2 = 'I am angry !'
_body_text_html2 = 'I am angry !<pre>Admin</pre>'
# 3 - Create in mass_mail composition mode that should work with or without email_template installed
# CASE1: create in mass_mail composition mode that should work with email_template installed
compose_id = mail_compose.create(cr, uid,
{'subject': _subject, 'body': _body_text},
{'subject': 'Forget me subject', 'body': 'Dummy body'},
{'default_composition_mode': 'mass_mail', 'default_model': 'mail.group',
'default_res_id': -1, 'default_use_template': True,
'active_ids': [self.group_pigs_id], 'default_template_id': email_template_id})
'active_ids': [self.group_pigs_id, group_bird_id], 'default_template_id': email_template_id})
compose = mail_compose.browse(cr, uid, compose_id)
# print compose.subject
# Try the 'onchange_template_id'
values = mail_compose.onchange_template_id(cr, uid, [], compose.use_template, compose.template_id, compose.composition_mode, compose.res_id)
# print values
values = mail_compose.onchange_use_template(cr, uid, [], not compose.use_template, compose.template_id, compose.composition_mode, compose.res_id)
# print values
values = mail_compose.onchange_template_id(cr, uid, [], compose.use_template, compose.template_id, compose.composition_mode, compose.model, compose.res_id)
print values
# compose.write(values['value'])
values = mail_compose.onchange_use_template(cr, uid, [], not compose.use_template, compose.template_id, compose.composition_mode, compose.model, compose.res_id)
print values
compose.refresh()
# # Post the comment, get created message
# mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id]})
# group_pigs.refresh()
# msg = group_pigs.message_ids[0]
# # Test: last message on Pigs = last created message
# test_msg = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [], limit=1))[0]
# self.assertEqual(msg.id, test_msg.id, 'Pigs did not receive its mass mailing message')
# # Test: mail.message: subject, body
# self.assertEqual(msg.subject, _subject, 'mail.message subject is incorrect')
# self.assertEqual(msg.body, group_pigs.description, 'mail.message body is incorrect')
# Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id, group_bird_id]})
group_pigs.refresh()
group_bird.refresh()
message_pigs = group_pigs.message_ids[0]
message_bird = group_bird.message_ids[0]
# Test: subject, body
self.assertEqual(message_pigs.subject, _subject1, 'mail.message subject on Pigs incorrect')
self.assertEqual(message_bird.subject, _subject2, 'mail.message subject on Bird incorrect')
self.assertEqual(message_pigs.body, _body_text_html1, 'mail.message body on Pigs incorrect')
self.assertEqual(message_bird.body, _body_text_html2, 'mail.message body on Bird incorrect')
# Test: partner_ids
print message_pigs.partner_ids
print message_pigs.partner_ids
self.assertEqual(len(message_pigs.partner_ids), 6, 'mail.message partner_ids incorrect')
@@ -65,29 +65,19 @@ class email_template_preview(osv.osv_memory):
return result
_columns = {
'res_id':fields.selection(_get_records, 'Sample Document'),
'res_id': fields.selection(_get_records, 'Sample Document'),
}
def on_change_res_id(self, cr, uid, ids, res_id, context=None):
if not res_id:
return {}
if not res_id: return {}
vals = {}
email_template = self.pool.get('email.template')
template_id = context and context.get('template_id')
template = email_template.get_email_template(cr, uid, template_id=template_id, record_id=res_id, context=context)
model = template.model
vals['email_to'] = self.render_template(cr, uid, template.email_to, model, res_id, context)
vals['email_cc'] = self.render_template(cr, uid, template.email_cc, model, res_id, context)
vals['reply_to'] = self.render_template(cr, uid, template.reply_to, model, res_id, context)
vals['subject'] = self.render_template(cr, uid, template.subject, model, res_id, context)
description = self.render_template(cr, uid, template.body, model, res_id, context) or ''
if template.user_signature:
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
description += '\n' + signature
vals['body'] = description
if template.body_html:
vals['body_html'] = self.render_template(cr, uid, template.body_html, model, res_id, context) or ''
vals['report_name'] = self.render_template(cr, uid, template.report_name, model, res_id, context)
template = email_template.browse(cr, uid, template_id, context=context)
vals['name'] = template.name
mail_values = email_template.generate_email(cr, uid, template_id, res_id, context=context)
for k in ('email_from','email_to','email_cc','reply_to','subject','body_html'):
vals[k] = mail_values[k]
return {'value': vals}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -6,26 +6,22 @@
<field name="name">email_template.preview.form</field>
<field name="model">email_template.preview</field>
<field name="arch" type="xml">
<form string="Email Preview">
<field name="model_id" invisible="1"/>
<field name="res_id" on_change="on_change_res_id(res_id, context)"/>
<group col="2" colspan="4">
<field name="email_to" readonly="1"/>
<field name="email_cc" readonly="1" attrs="{'invisible': [('email_cc','=',False)]}"/>
<field name="reply_to" readonly="1" attrs="{'invisible': [('reply_to','=',False)]}"/>
<field name="subject" readonly="1"/>
<form string="Email Preview" version="7.0">
<div>
<h2>Preview of <field name="name" readonly="1" class="oe_inline"/></h2>
<field name="model_id" invisible="1"/>
<h3>Using sample document
<field name="res_id" on_change="on_change_res_id(res_id, context)" class="oe_inline"/>
</h3>
</div>
<group>
<field name="email_from" readonly="1"/>
<field name="email_to" readonly="1"/>
<field name="email_cc" readonly="1" attrs="{'invisible':[('email_cc','=',False)]}"/>
<field name="reply_to" readonly="1" attrs="{'invisible':[('reply_to','=',False)]}"/>
<field name="subject" readonly="1"/>
<field name="body_html" widget="html" readonly="1"/>
</group>
<group col="4" colspan="4">
<notebook>
<page string="Body (Text)">
<field name="body" nolabel="1" colspan="4" height="350" width="350" readonly="1"/>
</page>
<page string="Body (Rich/HTML)" attrs="{'invisible': [('body_html','=', False)]}">
<field name="body_html" nolabel="1" colspan="4" height="350" width="350" readonly="1"/>
</page>
</notebook>
</group>
<field name="report_name" colspan="4" readonly="1"/>
</form>
</field>
</record>
@@ -27,13 +27,9 @@ from osv import fields
from tools.translate import _
class mail_compose_message(osv.osv_memory):
""" Inherit mail_compose_message to add email template feature in the
message composer. """
_inherit = 'mail.compose.message'
def _get_templates(self, cr, uid, context=None):
""" Return Email Template of particular Model. """
if context is None:
context = {}
model = False
@@ -52,7 +48,6 @@ class mail_compose_message(osv.osv_memory):
return []
def default_get(self, cr, uid, fields, context=None):
""" Override to handle templates. """
if context is None:
context = {}
result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
@@ -65,43 +60,36 @@ class mail_compose_message(osv.osv_memory):
'template_id': fields.selection(_get_templates, 'Template', size=-1),
}
def onchange_template_id(self, cr, uid, ids, use_template, template_id, composition_mode, res_id, context=None):
""" onchange_template_id: read or render the template if set, get back
to default values if not. """
def onchange_template_id(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
""" - use_template not set: return default_get
- use_template set:
- mass_mailing: we cannot render, so return the template values
- else: return rendered values """
fields = ['body', 'body_html', 'subject', 'partner_ids', 'email_to', 'email_cc']
if use_template and template_id:
# use the original template values, to be rendered when actually sent
if composition_mode == 'mass_mail':
values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
# render the mail as one-shot
else:
values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
# retrofit generated attachments in the expected field format
if values['attachments']:
attachment = values.pop('attachments')
attachment_obj = self.pool.get('ir.attachment')
att_ids = []
for fname, fcontent in attachment.iteritems():
data_attach = {
'name': fname,
'datas': fcontent,
'datas_fname': fname,
'description': fname,
'res_model' : self._name,
'res_id' : ids[0] if ids else False
}
att_ids.append(attachment_obj.create(cr, uid, data_attach))
values['attachment_ids'] = att_ids
else: # restore defaults
if use_template and template_id and composition_mode == 'mass_mail':
values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
elif use_template and template_id:
values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
values['attachment_ids'] = []
attachment_obj = self.pool.get('ir.attachment')
for attach_fname, attach_datas in values.get('attachments', []):
data_attach = {
'name': attach_fname,
'datas': attach_datas,
'datas_fname': attach_fname,
'description': fname,
'res_model': model,
'res_id': res_id,
}
values['attachment_ids'].append(attachment_obj.create(cr, uid, data_attach), context=context)
else:
values = self.default_get(cr, uid, fields, context=context)
if values.get('body_html') and not values.get('body'):
values['body'] = values.get('body_html')
values.update(use_template=use_template, template_id=template_id)
print 'returning ', values
return {'value': values}
def toggle_template(self, cr, uid, ids, context=None):
@@ -109,16 +97,15 @@ class mail_compose_message(osv.osv_memory):
emulate an on_change, then writes the value to update the form. """
for record in self.browse(cr, uid, ids, context=context):
onchange_res = self.onchange_use_template(cr, uid, ids, not record.use_template,
record.template_id, record.composition_mode, record.res_id, context=context)['value']
record.template_id, record.composition_mode, record.model, record.res_id, context=context)['value']
record.write(onchange_res)
return True
def onchange_use_template(self, cr, uid, ids, use_template, template_id, composition_mode, res_id, context=None):
def onchange_use_template(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
""" onchange_use_template (values: True or False). If use_template is
False, we do like an onchange with template_id False for values """
onchange_template_values = self.onchange_template_id(cr, uid, ids, use_template,
template_id, composition_mode, res_id, context=context)
return onchange_template_values
return self.onchange_template_id(cr, uid, ids, use_template,
template_id, composition_mode, model, res_id, context=context)
def save_as_template(self, cr, uid, ids, context=None):
""" hit save as template button: current form value will be a new
@@ -144,9 +131,36 @@ class mail_compose_message(osv.osv_memory):
record.write({'template_id': template_id, 'use_template': True})
return True
#------------------------------------------------------
# Wizard validation and send
#------------------------------------------------------
def render_message(self, cr, uid, wizard, model, res_id, context=None):
""" Generate an email from the template for given (model, res_id) pair.
This method is meant to be inherited by email_template that will
produce a more complete dictionary, with email_to, ...
"""
# render the template to get the email
template_values = self.pool.get('email.template').generate_email(cr, uid, wizard.template_id, res_id, context=context)
# TDE TODO: handle fields to update / not to update (mail_server_id, auto_delete, ...)
# transform email_to, email_cc into partner_ids
partner_ids = []
mails = tools.email_split(template_values.pop('email_to', '') + ' ' + template_values.pop('email_cc', ''))
for mail in mails:
partner_search_ids = self.pool.get('res.partner').search(cr, uid, [('email', 'ilike', mail)], context=context)
if partner_search_ids:
partner_ids.append((4, partner_search_ids[0]))
else:
partner_id = self.pool.get('res.partner').name_create(cr, uid, mail, context=context)[0]
partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
partner_ids.append((4, partner_id))
# get values to return
email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, model, res_id, context)
email_dict.update(template_values)
email_dict.update(partner_ids=partner_ids, attachments=template_values.get('attachments'))
return email_dict
def render_template(self, cr, uid, template, model, res_id, context=None):
""" Override of mail.compose.message behavior: use the power of
templates ! """
return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -19,7 +19,7 @@
<group attrs="{'invisible':[('use_template','=',False)]}" colspan="4" col="4">
<field name="use_template" invisible="1"/>
<field name="template_id" colspan="3"
on_change="onchange_template_id(use_template, template_id, composition_mode, res_id, context)"/>
on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
</group>
</xpath>
</data>
@@ -34,10 +34,10 @@
<data>
<xpath expr="//field[@name='partner_ids']" position="after">
<field name="use_template" colspan="2" nolabel="1" invisible="1"
on_change="onchange_use_template(use_template, template_id, composition_mode, res_id, context)"/>
on_change="onchange_use_template(use_template, template_id, composition_mode, model, res_id, context)"/>
<field name="template_id" colspan="2" nolabel="1"
attrs="{'invisible':[('use_template','=',False)]}"
on_change="onchange_template_id(use_template, template_id, composition_mode, res_id, context)"/>
on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
</xpath>
<xpath expr="//button[@class='oe_mail_compose_message_attachment']" position="before">
<button icon="/email_template/static/src/img/email_template.png"
+1 -1
View File
@@ -3,7 +3,7 @@ var _t = instance.web._t;
instance.web.Sidebar = instance.web.Sidebar.extend({
on_attachments_loaded: function(attachments) {
self = this
var self = this;
self._super(attachments);
// if attachment contains a google doc url do nothing
// else display a button to create a google doc
+44 -53
View File
@@ -25,6 +25,7 @@ import datetime, time
from itertools import groupby
from operator import itemgetter
import math
import netsvc
from osv import fields, osv
from tools.translate import _
@@ -112,7 +113,7 @@ class hr_holidays(osv.osv):
return result
_columns = {
'name': fields.char('Description', required=True, size=64),
'name': fields.char('Description', size=64),
'state': fields.selection([('draft', 'To Submit'), ('cancel', 'Cancelled'),('confirm', 'To Approve'), ('refuse', 'Refused'), ('validate1', 'Second Approval'), ('validate', 'Approved')],
'State', readonly=True, help='The state is set to \'To Submit\', when a holiday request is created.\
\nThe state is \'To Approve\', when holiday request is confirmed by user.\
@@ -123,8 +124,6 @@ class hr_holidays(osv.osv):
'date_to': fields.datetime('End Date', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
'holiday_status_id': fields.many2one("hr.holidays.status", "Leave Type", required=True,readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
'employee_id': fields.many2one('hr.employee', "Employee", select=True, invisible=False, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, help='Leave Manager can let this field empty if this leave request/allocation is for every employee'),
#'manager_id': fields.many2one('hr.employee', 'Leave Manager', invisible=False, readonly=True, help='This area is automatically filled by the user who validate the leave'),
#'notes': fields.text('Notes',readonly=True, states={'draft':[('readonly',False)]}),
'manager_id': fields.many2one('hr.employee', 'First Approval', invisible=False, readonly=True, help='This area is automatically filled by the user who validate the leave'),
'notes': fields.text('Reasons',readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
'number_of_days_temp': fields.float('Number of Days', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
@@ -134,8 +133,8 @@ class hr_holidays(osv.osv):
'parent_id': fields.many2one('hr.holidays', 'Parent'),
'linked_request_ids': fields.one2many('hr.holidays', 'parent_id', 'Linked Requests',),
'department_id':fields.related('employee_id', 'department_id', string='Department', type='many2one', relation='hr.department', readonly=True, store=True),
'category_id': fields.many2one('hr.employee.category', "Category", help='Category of Employee'),
'holiday_type': fields.selection([('employee','By Employee'),('category','By Employee Category')], 'Allocation Type', help='By Employee: Allocation/Request for individual Employee, By Employee Category: Allocation/Request for group of employees in category', required=True),
'category_id': fields.many2one('hr.employee.category', "Category", help='Category of Employee', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
'holiday_type': fields.selection([('employee','By Employee'),('category','By Employee Category')], 'Allocation Mode', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, help='By Employee: Allocation/Request for individual Employee, By Employee Category: Allocation/Request for group of employees in category', required=True),
'manager_id2': fields.many2one('hr.employee', 'Second Approval', readonly=True, help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)'),
'double_validation': fields.related('holiday_status_id', 'double_validation', type='boolean', relation='hr.holidays.status', string='Apply Double Validation'),
}
@@ -147,16 +146,11 @@ class hr_holidays(osv.osv):
'holiday_type': 'employee'
}
_sql_constraints = [
('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL))", "You have to select an employee or a category."),
('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL))", "The employee or employee category of this request is missing."),
('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be before the end date !"),
('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0 !"),
]
def create(self, cr, uid, vals, context=None):
obj_id = super(hr_holidays, self).create(cr, uid, vals, context=context)
self.create_notificate(cr, uid, [obj_id], context=context)
return obj_id
def _create_resource_leave(self, cr, uid, leaves, context=None):
'''This method will create entry in resource calendar leave object at the time of holidays validated '''
obj_res_leave = self.pool.get('resource.calendar.leaves')
@@ -201,8 +195,8 @@ class hr_holidays(osv.osv):
def unlink(self, cr, uid, ids, context=None):
for rec in self.browse(cr, uid, ids, context=context):
if rec.state<>'draft':
raise osv.except_osv(_('Warning!'),_('You cannot delete a leave which is not in draft state !'))
if rec.state not in ['draft', 'cancel', 'confirm']:
raise osv.except_osv(_('Warning!'),_('You cannot delete a leave which is in %s state!')%(rec.state))
return super(hr_holidays, self).unlink(cr, uid, ids, context)
def onchange_date_from(self, cr, uid, ids, date_to, date_from):
@@ -210,7 +204,7 @@ class hr_holidays(osv.osv):
if date_to and date_from:
diff_day = self._get_number_of_days(date_from, date_to)
result['value'] = {
'number_of_days_temp': round(diff_day)+1
'number_of_days_temp': round(math.floor(diff_day))+1
}
return result
result['value'] = {
@@ -244,29 +238,30 @@ class hr_holidays(osv.osv):
self.unlink(cr, uid, to_unlink, context=context)
return True
def holidays_validate(self, cr, uid, ids, context=None):
def holidays_first_validate(self, cr, uid, ids, context=None):
self.check_holidays(cr, uid, ids, context=context)
obj_emp = self.pool.get('hr.employee')
ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
manager = ids2 and ids2[0] or False
self.holidays_validate_notificate(cr, uid, ids, context=context)
self.holidays_first_validate_notificate(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state':'validate1', 'manager_id': manager})
def holidays_validate2(self, cr, uid, ids, context=None):
def holidays_validate(self, cr, uid, ids, context=None):
self.check_holidays(cr, uid, ids, context=context)
obj_emp = self.pool.get('hr.employee')
ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
manager = ids2 and ids2[0] or False
self.write(cr, uid, ids, {'state':'validate'})
data_holiday = self.browse(cr, uid, ids)
holiday_ids = []
for record in data_holiday:
if record.holiday_status_id.double_validation:
holiday_ids.append(record.id)
if record.double_validation:
self.write(cr, uid, [record.id], {'manager_id2': manager})
else:
self.write(cr, uid, [record.id], {'manager_id': manager})
if record.holiday_type == 'employee' and record.type == 'remove':
meeting_obj = self.pool.get('crm.meeting')
meeting_vals = {
'name': record.name,
'name': record.name or _('Leave Request'),
'categ_ids': record.holiday_status_id.categ_id and [(6,0,[record.holiday_status_id.categ_id.id])] or [],
'duration': record.number_of_days_temp * 8,
'description': record.notes,
@@ -301,25 +296,27 @@ class hr_holidays(osv.osv):
wf_service.trg_validate(uid, 'hr.holidays', leave_id, 'confirm', cr)
wf_service.trg_validate(uid, 'hr.holidays', leave_id, 'validate', cr)
wf_service.trg_validate(uid, 'hr.holidays', leave_id, 'second_validate', cr)
if holiday_ids:
self.holidays_valid2_notificate(cr, uid, holiday_ids, context=context)
self.write(cr, uid, holiday_ids, {'manager_id2': manager})
self.holidays_validate_notificate(cr, uid, ids, context=context)
return True
def holidays_confirm(self, cr, uid, ids, context=None):
self.check_holidays(cr, uid, ids, context=context)
for record in self.browse(cr, uid, ids, context=context):
if record.employee_id and record.employee_id.parent_id and record.employee_id.parent_id.user_id:
self.message_subscribe(cr, uid, [record.id], user_ids=[record.employee_id.parent_id.user_id.id], context=context)
self.holidays_confirm_notificate(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state':'confirm'})
def holidays_refuse(self, cr, uid, ids, approval, context=None):
def holidays_refuse(self, cr, uid, ids, context=None):
obj_emp = self.pool.get('hr.employee')
ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
manager = ids2 and ids2[0] or False
if approval == 'first_approval':
self.write(cr, uid, ids, {'state': 'refuse', 'manager_id': manager})
else:
self.write(cr, uid, ids, {'state': 'refuse', 'manager_id2': manager})
self.holidays_refuse_notificate(cr, uid, ids, approval, context=context)
for holiday in self.browse(cr, uid, ids, context=context):
if holiday.state == 'validate1':
self.write(cr, uid, [holiday.id], {'state': 'refuse', 'manager_id': manager})
else:
self.write(cr, uid, [holiday.id], {'state': 'refuse', 'manager_id2': manager})
self.holidays_refuse_notificate(cr, uid, ids, context=context)
self.holidays_cancel(cr, uid, ids, context=context)
return True
@@ -333,7 +330,7 @@ class hr_holidays(osv.osv):
# If a category that created several holidays, cancel all related
wf_service = netsvc.LocalService("workflow")
for request in record.linked_request_ids or []:
wf_service.trg_validate(uid, 'hr.holidays', request.id, 'cancel', cr)
wf_service.trg_validate(uid, 'hr.holidays', request.id, 'refuse', cr)
self._remove_resource_leave(cr, uid, ids, context=context)
return True
@@ -367,39 +364,33 @@ class hr_holidays(osv.osv):
def create_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, ids,
_("The %s request has been <b>created</b> and is waiting for confirmation.")
% ('leave' if obj.type == 'remove' else 'allocation',), context=context)
_("The request has been <b>created</b> and is waiting confirmation."), context=context)
return True
def holidays_confirm_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids):
self.message_post(cr, uid, [obj.id], _("The %s request has been <b>confirmed</b> and is waiting for validation by the manager.")
% ('leave' if obj.type == 'remove' else 'allocation',), context=context)
self.message_post(cr, uid, [obj.id],
_("The request has been <b>submitted</b> and is waiting for validation by the manager."), context=context)
def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id],
_("The request has been <b>approved</b>. A second validation is necessary and is now pending."), context=context)
def holidays_validate_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids):
if obj.holiday_status_id.double_validation:
if obj.double_validation:
self.message_post(cr, uid, [obj.id],
_("The %s request has been <b>approved</b>. A second validation is necessary and is now pending.")
% ('leave' if obj.type == 'remove' else 'allocation',), context=context)
_("The request has been <b>double validated</b>. The validation process is now over."), context=context)
else:
self.message_post(cr, uid, [obj.id],
_("The %s request has been <b>approved</b>. The validation process is now over.")
% ('leave' if obj.type == 'remove' else 'allocation',), context=context)
self.message_post(cr, uid, [obj.id],
_("The request has been <b>approved</b>. The validation process is now over."), context=context)
def holidays_valid2_notificate(self, cr, uid, ids, context=None):
def holidays_refuse_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids):
self.message_post(cr, uid, [obj.id],
_("The %s request has been <b>double validated</b>. The validation process is now over.")
% ('leave' if obj.type == 'remove' else 'allocation',), context=context)
def holidays_refuse_notificate(self, cr, uid, ids, approval, context=None):
for obj in self.browse(cr, uid, ids):
self.message_post(cr, uid, [obj.id],
_("The %s request has been <b>refused</b>. The validation process is now over.")
% ('leave' if obj.type == 'remove' else 'allocation',), context=context)
hr_holidays()
self.message_post(cr, uid, [obj.id],
_("The request has been <b>refused</b>. The validation process is now over."), context=context)
class resource_calendar_leaves(osv.osv):
_inherit = "resource.calendar.leaves"
+1 -4
View File
@@ -12,7 +12,7 @@
<record model="hr.holidays" id="hr_holidays_employee1_cl">
<field name="name">Trip with Family</field>
<field name="holiday_status_id" ref="holiday_status_cl"/>
<field name="holiday_status_id" ref="holiday_status_comp"/>
<field eval="time.strftime('%Y-%m-01')" name="date_from"/>
<field eval="time.strftime('%Y-%m-03')" name="date_to"/>
<field name="type">remove</field>
@@ -52,13 +52,10 @@
<field name="employee_id" ref="hr.employee_fp"/>
</record>
<workflow action="confirm" model="hr.holidays" ref="hr_holidays_employee1_allocation_cl"/>
<workflow action="validate" model="hr.holidays" ref="hr_holidays_employee1_allocation_cl"/>
<workflow action="confirm" model="hr.holidays" ref="hr_holidays_employee1_sl"/>
<workflow action="validate" model="hr.holidays" ref="hr_holidays_employee1_sl"/>
<workflow action="confirm" model="hr.holidays" ref="hr_holidays_employee1_int_tour"/>
<workflow action="validate" model="hr.holidays" ref="hr_holidays_employee1_int_tour"/>
</data>
</openerp>
+30 -55
View File
@@ -83,56 +83,42 @@
</field>
</record>
<record model="ir.ui.view" id="edit_holiday_new">
<record model="ir.ui.view" id="edit_holiday_new">
<field name="name">Leave Request</field>
<field name="model">hr.holidays</field>
<field name="priority">1</field>
<field name="arch" type="xml">
<form string="Leave Request" version="7.0">
<header>
<button string="Submit to Manager" name="confirm" states="draft" type="workflow" class="oe_highlight"/>
<button string="Approve" name="validate" states="confirm" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
<button string="Validate" name="second_validate" states="validate1" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
<button string="Refuse" name="refuse" states="confirm,validate1" type="workflow" groups="base.group_hr_user,base.group_hr_manager" class="oe_highlight"/>
<button string="Reset to New" name="set_to_draft" states="refuse,validate" type="object" groups="base.group_hr_user" />
<button string="Refuse" name="refuse" states="confirm,validate1,validate" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
<button string="Reset to New" name="set_to_draft" states="refuse" type="object" groups="base.group_hr_user" />
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate" statusbar_colors='{"confirm":"blue","validate1":"blue","refuse":"red"}'/>
</header>
<sheet string="Leave Request">
<group>
<group>
<field name="name" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')]}"/>
<field name="holiday_type" on_change="onchange_type(holiday_type)" attrs="{'readonly':[('state','!=','draft')]}" width="130" string="Mode" groups="base.group_hr_user"/>
<field name="employee_id" attrs="{'required':[('holiday_type','=','employee')],'invisible':[('holiday_type','=','category')]}" groups="base.group_hr_user"/>
<field name="category_id" attrs="{'required':[('holiday_type','=','category')], 'invisible':[('holiday_type','=','employee')], 'readonly':[('state','!=','draft'), ('state','!=','confirm')]}"/>
<field name="department_id" attrs="{'readonly':[('holiday_type','=','category')]}" groups="base.group_hr_user"/>
</group>
<group>
<field name="holiday_status_id" on_change="onchange_status_id(holiday_status_id)" context="{'employee_id':employee_id}"/>
<label for="number_of_days_temp" string="Duration"/>
<div>
<field name="date_from" on_change="onchange_date_from(date_to, date_from)" required="1" class="oe_inline"/> -
<field name="date_to" on_change="onchange_date_from(date_to, date_from)" required="1" class="oe_inline"/>
<group col="3">
<field name="date_from" nolabel="1" on_change="onchange_date_from(date_to, date_from)" required="1" class="oe_inline"/><label string="-" class="oe_inline" />
<field name="date_to" nolabel="1" on_change="onchange_date_from(date_to, date_from)" required="1" class="oe_inline"/>
</group>
<div>
<field name="number_of_days_temp" class="oe_inline"/> days
</div>
</div>
<field name="category_id" attrs="{'required':[('holiday_type','=','category')], 'invisible':[('holiday_type','=','employee')], 'readonly':[('state','!=','draft'), ('state','!=','confirm')]}"/>
</group>
<group>
<field name="holiday_type" on_change="onchange_type(holiday_type)" attrs="{'readonly':[('state','!=','draft')]}" width="130" string="Mode" groups="base.group_hr_user"/>
<field name="employee_id" attrs="{'required':[('holiday_type','=','employee')],'invisible':[('holiday_type','=','category')]}" groups="base.group_hr_user"/>
<field name="department_id" attrs="{'readonly':[('holiday_type','=','category')]}" groups="base.group_hr_user"/>
</group>
</group>
<notebook colspan="4">
<page string="General">
<group>
<group>
<field name="manager_id" groups="base.group_no_one"/>
<field name="double_validation" invisible="1"/>
</group>
<group>
<field name="manager_id2" attrs="{'invisible':[('double_validation', '!=', True)]}" groups="base.group_no_one"/>
</group>
</group>
<newline/>
<field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..."/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
@@ -149,37 +135,27 @@
<field name="arch" type="xml">
<form string="Allocation Request" version="7.0">
<header>
<button string="Submit to Manager" name="confirm" states="draft" type="workflow" class="oe_highlight"/>
<button string="Approve" name="validate" states="confirm" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
<button string="Validate" name="second_validate" states="validate1" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
<button string="Refuse" name="refuse" states="confirm,validate1" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
<button string="Reset to New" name="set_to_draft" states="cancel,validate,refuse" type="object" groups="base.group_hr_user" />
<button string="Refuse" name="refuse" states="confirm,validate,validate1" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
<button string="Reset to New" name="set_to_draft" states="cancel,refuse" type="object" groups="base.group_hr_user" />
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate" statusbar_colors='{"confirm":"blue","validate1":"blue","refuse":"red"}'/>
</header>
<sheet>
<group>
<group>
<field name="name"/>
<field name="holiday_type" on_change="onchange_type(holiday_type)" attrs="{'readonly':[('state','!=','draft')]}" string="Allocation Mode" groups="base.group_hr_manager"/>
<field name="employee_id" attrs="{'required':[('holiday_type','=','employee')], 'invisible':[('holiday_type','=','category')]}"/>
<field name="category_id" attrs="{'required':[('holiday_type','=','category')], 'readonly':[('state','!=','draft')], 'invisible':[('holiday_type','=','employee')]}"/>
<field name="department_id" attrs="{'readonly':['|', ('holiday_type','=','category'), ('state','not in' ,('draft', 'confirm'))]}"/>
</group>
<group>
<field name="name" required="1" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')]}"/>
<field name="holiday_status_id" on_change="onchange_status_id(holiday_status_id)" context="{'employee_id':employee_id}"/>
<field name="number_of_days_temp"/>
</group>
<group>
<field name="holiday_type" on_change="onchange_type(holiday_type)"/>
<field name="employee_id" attrs="{'required':[('holiday_type','=','employee')], 'invisible':[('holiday_type','=','category')]}"/>
<field name="category_id" attrs="{'required':[('holiday_type','=','category')], 'invisible':[('holiday_type','=','employee')]}"/>
<field name="department_id" attrs="{'invisible':[('holiday_type','=','category')]}"/>
</group>
</group>
<notebook>
<page string="General">
<group col="4">
<field name="double_validation" invisible="1"/>
<field name="manager_id"/>
<field name="manager_id2" attrs="{'invisible':[('double_validation', '!=', True)]}"/>
</group>
<field name="notes" placeholder="Add a reason..."/>
</page>
</notebook>
<field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..."/>
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
@@ -223,7 +199,6 @@
<field name="state"/>
</header>
<group col="4">
<field name="name"/>
<field name="holiday_status_id"/>
<field name="type"/>
<field name="date_from" on_change="onchange_date_from(date_to, date_from)" attrs="{'readonly':[('type','=','add')], 'required':[('type','=','remove')]}"/>
@@ -231,7 +206,7 @@
<field name="number_of_days_temp"/>
<field name="manager_id"/>
</group>
<field name="notes"/>
<field name="name" placeholder="Add a reason..."/>
</form>
</field>
</record>
@@ -284,12 +259,12 @@
<menuitem name="Leaves" parent="hr.menu_hr_root" id="menu_open_ask_holidays" sequence="20"/>
<record model="ir.actions.act_window" id="open_ask_holidays">
<field name="name">Leave Requests</field>
<field name="name">My Leave Requests</field>
<field name="res_model">hr.holidays</field>
<field name="view_type">form</field>
<field name="view_id" ref="edit_holiday_new"/>
<field name="context">{}</field>
<field name="domain">[('type','=','remove')]</field>
<field name="domain">[('type','=','remove'),('employee_id.user_id','=', uid)]</field>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
@@ -323,7 +298,7 @@
<field name="view_id" ref="view_holiday_new_calendar"/>
<field name="act_window_id" ref="open_ask_holidays"/>
</record>
<menuitem name="My Leave Requests" parent="menu_open_ask_holidays" id="menu_open_ask_holidays_new" action="open_ask_holidays"/>
<record model="ir.actions.act_window" id="request_approve_holidays">
@@ -353,11 +328,11 @@
<menuitem name="Leave Requests to Approve" parent="menu_open_ask_holidays" id="menu_request_approve_holidays" action="request_approve_holidays" groups="base.group_hr_user"/>
<record model="ir.actions.act_window" id="open_allocation_holidays">
<field name="name">Allocation Requests</field>
<field name="name">My Allocation Requests</field>
<field name="res_model">hr.holidays</field>
<field name="view_type">form</field>
<field name="context">{'default_type':'add'}</field>
<field name="domain">[('type','=','add')]</field>
<field name="domain">[('type','=','add'),('employee_id.user_id','=', uid)]</field>
<field name="view_id" ref="allocation_leave_new"/>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
@@ -376,7 +351,7 @@
<field name="act_window_id" ref="open_allocation_holidays"/>
</record>
<menuitem name="Allocation Requests" parent="menu_open_ask_holidays" id="menu_open_allocation_holidays" action="open_allocation_holidays"/>
<menuitem name="My Allocation Requests" parent="menu_open_ask_holidays" id="menu_open_allocation_holidays" action="open_allocation_holidays"/>
<record model="ir.actions.act_window" id="open_company_allocation">
<field name="name">Leaves Summary</field>
+59 -30
View File
@@ -2,7 +2,16 @@
<openerp>
<data>
<!-- Workflow definition -->
<!-- Workflow definition
1. draft->submitted (no signal)
2. submitted->accepted (validate signal) if not double_validation
2. submitted -> first_accepted (validate signal) if double_validation
2. submitted->refused (refuse signal)
3. accepted->refused (refuse signal)
4. first_accepted -> accepted (second_validate signal)
4. first_accepted -> refused (refuse signal)
-->
<record model="workflow" id="wkf_holidays">
<field name="name">hr.wkf.holidays</field>
@@ -10,13 +19,13 @@
<field name="on_create">True</field>
</record>
<record model="workflow.activity" id="act_draft">
<record model="workflow.activity" id="act_draft"> <!-- draft -->
<field name="wkf_id" ref="wkf_holidays" />
<field name="flow_start">True</field>
<field name="name">draft</field>
</record>
<record model="workflow.activity" id="act_confirm">
<record model="workflow.activity" id="act_confirm"> <!-- submitted -->
<field name="wkf_id" ref="wkf_holidays" />
<field name="name">confirm</field>
<field name="kind">function</field>
@@ -24,74 +33,94 @@
<field name="split_mode">OR</field>
</record>
<record model="workflow.activity" id="act_validate">
<record model="workflow.activity" id="act_validate"> <!-- accepted -->
<field name="wkf_id" ref="wkf_holidays" />
<field name="name">validate</field>
<field name="kind">function</field>
<field name="action">holidays_validate()</field>
</record>
<record model="workflow.activity" id="act_validate1">
<record model="workflow.activity" id="act_validate1"> <!-- first_accepted -->
<field name="wkf_id" ref="wkf_holidays" />
<field name="name">second_validate</field>
<field name="name">first_validate</field>
<field name="kind">function</field>
<field name="action">holidays_validate2()</field>
<field name="action">holidays_first_validate()</field>
<field name="split_mode">OR</field>
</record>
<record model="workflow.activity" id="act_refuse">
<record model="workflow.activity" id="act_refuse"> <!-- refused -->
<field name="wkf_id" ref="wkf_holidays" />
<field name="name">refuse</field>
<field name="flow_stop">True</field>
<field name="kind">stopall</field>
<field name="kind">function</field>
<field name="action">holidays_refuse()</field>
</record>
<!--
workflow transition
-->
<record model="workflow.transition" id="t1">
<record model="workflow.transition" id="holiday_draft2confirm"> <!-- 1. draft->submitted (no signal) -->
<field name="act_from" ref="act_draft" />
<field name="act_to" ref="act_confirm" />
<field name="signal">confirm</field>
</record>
<record model="workflow.transition" id="t2">
<record model="workflow.transition" id="holiday_confirm2validate"> <!-- 2. submitted->accepted (validate signal) if not double_validation-->
<field name="act_from" ref="act_confirm" />
<field name="act_to" ref="act_validate" />
<field name="signal">validate</field>
<field name="condition">not double_validation</field>
<field name="group_id" ref="base.group_hr_user"/>
</record>
<record model="workflow.transition" id="holiday_confirm2validate1"> <!-- 2. submitted -> first_accepted (validate signal) if double_validation-->
<field name="act_from" ref="act_confirm" />
<field name="act_to" ref="act_validate1" />
<field name="condition">double_validation</field>
<field name="group_id" ref="base.group_hr_user"/>
</record>
<record model="workflow.transition" id="holiday_confirm2refuse"> <!-- 2. submitted->refused (refuse signal) -->
<field name="act_from" ref="act_confirm" />
<field name="act_to" ref="act_refuse" />
<field name="signal">refuse</field>
<field name="condition">True</field>
<field name="group_id" ref="base.group_hr_user"/>
</record>
<record model="workflow.transition" id="t3">
<field name="act_from" ref="act_confirm" />
<field name="act_to" ref="act_refuse" />
<field name="signal">refuse</field>
<field name="condition">holidays_refuse('first_approval')</field>
<field name="group_id" ref="base.group_hr_user"/>
</record>
<record model="workflow.transition" id="t4">
<record model="workflow.transition" id="holiday_validate2refuse"> <!-- 3. accepted->refused (refuse signal) -->
<field name="act_from" ref="act_validate" />
<field name="act_to" ref="act_refuse" />
<field name="signal">refuse</field>
<field name="condition">holidays_refuse('second_approval')</field>
<field name="condition">True</field>
<field name="group_id" ref="base.group_hr_user"/>
</record>
<record model="workflow.transition" id="t8">
<record model="workflow.transition" id="holiday_validate2refuse"> <!-- 3. accepted->refused (refuse signal) -->
<field name="act_from" ref="act_validate" />
<field name="act_to" ref="act_validate1" />
<field name="act_to" ref="act_refuse" />
<field name="signal">refuse</field>
<field name="condition">True</field>
<field name="group_id" ref="base.group_hr_user"/>
</record>
<record model="workflow.transition" id="holiday_validate1_validate"> <!-- 4. first_accepted -> accepted (second_validate signal) -->
<field name="act_from" ref="act_validate1" />
<field name="act_to" ref="act_validate" />
<field name="condition">True</field>
<field name="signal">second_validate</field>
<field name="group_id" ref="base.group_hr_manager" />
<field name="group_id" ref="base.group_hr_user"/>
</record>
<record model="workflow.transition" id="t11">
<field name="act_from" ref="act_validate" />
<field name="act_to" ref="act_validate1" />
<field name="condition">not holiday_status_id.double_validation</field>
<field name="signal" eval="False"/>
<record model="workflow.transition" id="holiday_validate1_refuse"> <!-- 4. first_accepted->refused (refuse signal) -->
<field name="act_from" ref="act_validate1" />
<field name="act_to" ref="act_refuse" />
<field name="signal">refuse</field>
<field name="condition">True</field>
<field name="group_id" ref="base.group_hr_user"/>
</record>
</data>
@@ -4,5 +4,5 @@ access_hr_holidays_user,hr.holidays.user,model_hr_holidays,base.group_hr_user,1,
access_hr_holidays_employee,hr.holidays.employee,model_hr_holidays,base.group_user,1,1,1,1
access_hr_holydays_status_employee,hr.holidays.status employee,model_hr_holidays_status,base.group_user,1,0,0,0
access_hr_holidays_remain_user,hr.holidays.ramain.user,model_hr_holidays_remaining_leaves_user,base.group_hr_user,1,1,1,1
access_resource_calendar_leaves_manager,resource_calendar_leaves_manager,resource.model_resource_calendar_leaves,base.group_hr_manager,1,1,1,1
access_resource_calendar_leaves_user,resource_calendar_leaves_user,resource.model_resource_calendar_leaves,base.group_hr_user,1,1,1,1
access_crm_meeting_type_manager,crm.meeting.type.manager,base_calendar.model_crm_meeting_type,base.group_hr_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
4 access_hr_holidays_employee hr.holidays.employee model_hr_holidays base.group_user 1 1 1 1
5 access_hr_holydays_status_employee hr.holidays.status employee model_hr_holidays_status base.group_user 1 0 0 0
6 access_hr_holidays_remain_user hr.holidays.ramain.user model_hr_holidays_remaining_leaves_user base.group_hr_user 1 1 1 1
7 access_resource_calendar_leaves_manager access_resource_calendar_leaves_user resource_calendar_leaves_manager resource_calendar_leaves_user resource.model_resource_calendar_leaves base.group_hr_manager base.group_hr_user 1 1 1 1
8 access_crm_meeting_type_manager crm.meeting.type.manager base_calendar.model_crm_meeting_type base.group_hr_manager 1 1 1 1
+14 -1
View File
@@ -1,6 +1,19 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="property_rule_holidays_employee" model="ir.rule">
<field name="name">Employee Holidays</field>
<field model="ir.model" name="model_id" ref="model_hr_holidays"/>
<field name="domain_force">[('employee_id.user_id','=',user.id)]</field>
<field name="groups" eval="[(4,ref('base.group_user'))]"/>
</record>
<record id="property_rule_holidays_officer" model="ir.rule">
<field name="name">Holidays Officer</field>
<field model="ir.model" name="model_id" ref="model_hr_holidays"/>
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4,ref('base.group_hr_user'))]"/>
</record>
</data>
</openerp>
+217
View File
@@ -0,0 +1,217 @@
# Mongolian 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 <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 00:36+0000\n"
"PO-Revision-Date: 2012-08-31 07:21+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Mongolian <mn@li.org>\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-01 04:55+0000\n"
"X-Generator: Launchpad (build 15890)\n"
#. module: import_google
#: help:synchronize.google.import,group_name:0
msgid "Choose which group to import, By default it takes all."
msgstr ""
#. module: import_google
#: view:synchronize.google.import:0
msgid "Import Google Calendar Events"
msgstr ""
#. module: import_google
#: view:synchronize.google.import:0
msgid "_Import Events"
msgstr ""
#. module: import_google
#: code:addons/import_google/wizard/import_google_data.py:71
#, python-format
msgid ""
"No Google Username or password Defined for user.\n"
"Please define in user view"
msgstr ""
#. module: import_google
#: code:addons/import_google/wizard/import_google_data.py:127
#, python-format
msgid ""
"Invalid login detail !\n"
" Specify Username/Password."
msgstr ""
#. module: import_google
#: field:synchronize.google.import,supplier:0
msgid "Supplier"
msgstr ""
#. module: import_google
#: view:synchronize.google.import:0
msgid "Import Options"
msgstr ""
#. module: import_google
#: field:synchronize.google.import,group_name:0
msgid "Group Name"
msgstr ""
#. module: import_google
#: model:ir.model,name:import_google.model_crm_case_categ
msgid "Category of Case"
msgstr ""
#. module: import_google
#: model:ir.actions.act_window,name:import_google.act_google_login_contact_form
#: model:ir.ui.menu,name:import_google.menu_sync_contact
msgid "Import Google Contacts"
msgstr ""
#. module: import_google
#: view:google.import.message:0
msgid "Import Google Data"
msgstr ""
#. module: import_google
#: view:crm.meeting:0
msgid "Meeting Type"
msgstr ""
#. module: import_google
#: code:addons/import_google/wizard/import_google.py:38
#: code:addons/import_google/wizard/import_google_data.py:28
#, python-format
msgid ""
"Please install gdata-python-client from http://code.google.com/p/gdata-"
"python-client/downloads/list"
msgstr ""
#. module: import_google
#: model:ir.model,name:import_google.model_google_login
msgid "Google Contact"
msgstr ""
#. module: import_google
#: view:synchronize.google.import:0
msgid "Import contacts from a google account"
msgstr ""
#. module: import_google
#: code:addons/import_google/wizard/import_google_data.py:133
#, python-format
msgid "Please specify correct user and password !"
msgstr ""
#. module: import_google
#: field:synchronize.google.import,customer:0
msgid "Customer"
msgstr ""
#. module: import_google
#: view:synchronize.google.import:0
msgid "_Cancel"
msgstr ""
#. module: import_google
#: model:ir.model,name:import_google.model_synchronize_google_import
msgid "synchronize.google.import"
msgstr ""
#. module: import_google
#: view:synchronize.google.import:0
msgid "_Import Contacts"
msgstr ""
#. module: import_google
#: model:ir.actions.act_window,name:import_google.act_google_login_form
#: model:ir.ui.menu,name:import_google.menu_sync_calendar
msgid "Import Google Calendar"
msgstr ""
#. module: import_google
#: code:addons/import_google/wizard/import_google_data.py:50
#, python-format
msgid "Import google"
msgstr ""
#. module: import_google
#: code:addons/import_google/wizard/import_google_data.py:127
#: code:addons/import_google/wizard/import_google_data.py:133
#, python-format
msgid "Error"
msgstr ""
#. module: import_google
#: code:addons/import_google/wizard/import_google_data.py:71
#, python-format
msgid "Warning !"
msgstr ""
#. module: import_google
#: field:synchronize.google.import,create_partner:0
msgid "Options"
msgstr "Тохируулга"
#. module: import_google
#: view:google.import.message:0
msgid "_Ok"
msgstr ""
#. module: import_google
#: code:addons/import_google/wizard/import_google.py:38
#: code:addons/import_google/wizard/import_google_data.py:28
#, python-format
msgid "Google Contacts Import Error!"
msgstr ""
#. module: import_google
#: model:ir.model,name:import_google.model_google_import_message
msgid "Import Message"
msgstr ""
#. module: import_google
#: field:synchronize.google.import,calendar_name:0
msgid "Calendar Name"
msgstr ""
#. module: import_google
#: help:synchronize.google.import,supplier:0
msgid "Check this box to set newly created partner as Supplier."
msgstr ""
#. module: import_google
#: selection:synchronize.google.import,create_partner:0
msgid "Import only address"
msgstr "Зөвхөн хаяг оруулах"
#. module: import_google
#: field:crm.case.categ,user_id:0
msgid "User"
msgstr ""
#. module: import_google
#: view:synchronize.google.import:0
msgid "Partner Status for this Group:"
msgstr ""
#. module: import_google
#: field:google.import.message,name:0
msgid "Message"
msgstr ""
#. module: import_google
#: selection:synchronize.google.import,create_partner:0
msgid "Create partner for each contact"
msgstr ""
#. module: import_google
#: help:synchronize.google.import,customer:0
msgid "Check this box to set newly created partner as Customer."
msgstr ""
+3 -2
View File
@@ -29,6 +29,7 @@ import decimal_precision as dp
from tools.misc import currency
from tools.translate import _
from tools import config
from openerp import SUPERUSER_ID
class account_tax_code_template(osv.osv):
@@ -53,7 +54,7 @@ class account_tax_template(osv.osv):
def get_precision_tax():
def change_digit_tax(cr):
res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, 'Account')
res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, SUPERUSER_ID, 'Account')
return (16, res+2)
return change_digit_tax
@@ -91,7 +92,7 @@ class account_tax(osv.osv):
def get_precision_tax():
def change_digit_tax(cr):
res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, 'Account')
res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, SUPERUSER_ID, 'Account')
return (16, res+2)
return change_digit_tax
+1 -2
View File
@@ -92,9 +92,8 @@ class mail_alias(osv.Model):
_defaults = {
'alias_defaults': '{}',
'alias_user_id': lambda self,cr,uid,context: uid,
# looks better when creating new aliases - even if the field is informative only
'alias_domain': lambda self,cr,uid,context: self._get_alias_domain(cr,1,[1],None,None)[1]
'alias_domain': lambda self,cr,uid,context: self._get_alias_domain(cr, SUPERUSER_ID,[1],None,None)[1]
}
_sql_constraints = [
+1 -6
View File
@@ -102,12 +102,7 @@ class mail_notification(osv.Model):
body_html = msg.body
signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
if signature:
signature_block = u'\n<pre>%s</pre>\n' % signature
insertion_point = body_html.find('</html>')
if insertion_point > -1:
body_html = body_html[:insertion_point] + signature_block + body_html[insertion_point:]
else:
body_html += signature_block
body_html = tools.append_content_to_html(body_html, signature)
towrite = {
'mail_message_id': msg.id,
+4 -4
View File
@@ -59,7 +59,7 @@
</record>
<record model="ir.ui.view" id="view_mail_tree">
<field name="name">mail.message.tree</field>
<field name="name">mail.mail.tree</field>
<field name="model">mail.mail</field>
<field name="arch" type="xml">
<tree string="Emails" colors="grey:state in ('sent', 'cancel');blue:state=='outgoing';red:state=='exception';black:state=='received'">
@@ -113,15 +113,15 @@
</record>
<record id="action_view_mail_mail" model="ir.actions.act_window">
<field name="name">Messages</field>
<field name="name">Emails</field>
<field name="res_model">mail.mail</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_received': 1, 'search_default_type_email': 1}</field>
<field name="context">{'search_default_outgoing': 1, 'search_default_type_email': 1}</field>
<field name="search_view_id" ref="view_mail_search"/>
</record>
<!-- Add menu entry in Settings/Email -->
<menuitem name="Mails" id="menu_mail_mail" parent="base.menu_email" action="action_view_mail_mail" />
<menuitem name="Emails" id="menu_mail_mail" parent="base.menu_email" action="action_view_mail_mail" />
</data>
</openerp>
+18 -32
View File
@@ -167,12 +167,10 @@ class mail_message(osv.Model):
def message_read_tree_flatten(self, cr, uid, messages, current_level, level, context=None):
""" Given a tree with several roots of following structure :
[
{'id': 1, 'child_ids':[
{'id': 11, 'child_ids': [...] },
] },
{...}
]
[ {'id': 1, 'child_ids': [
{'id': 11, 'child_ids': [...] },],
},
{...} ]
Flatten it to have a maximum number of level, with 0 being
completely flat.
Perform the flattening at leafs if above the maximum depth, then get
@@ -187,40 +185,29 @@ class mail_message(osv.Model):
return [msg_dict] + child_ids
# Depth-first flattening
for message in messages:
if message.get('type') == 'expandable':
continue
message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level+1, level, context=context)
# Flatten if above maximum depth
if current_level < level:
return messages
new_list = []
for x in range(0, len(messages)):
flatenned = _flatten(messages[x])
for flat in flatenned:
new_list.append(flat)
messages = new_list
return messages
def _debug_print_tree(self, tree, prefix=''):
for elem in tree:
print '%s%s (%s childs: %s)' % (prefix, elem['id'], len(elem['child_ids']), [xelem['id'] for xelem in elem['child_ids']])
if elem['child_ids']:
self._debug_print_tree(elem['child_ids'], prefix+'--')
for message in messages:
for flat_message in _flatten(message):
new_list.append(flat_message)
return new_list
def message_read(self, cr, uid, ids=False, domain=[], thread_level=0, limit=None, context=None):
"""
If IDS are provided, fetch these records, otherwise use the domain to
fetch the matching records. After having fetched the records provided
by IDS, it will fetch children (according to thread_level).
Return [
]
""" If IDs are provided, fetch these records. Otherwise use the domain
to fetch the matching records.
After having fetched the records provided by IDs, it will fetch the
parents to have well-formed threads.
:return list: list of trees of messages
"""
limit = limit or self._message_read_limit
context = context or {}
if ids is False:
if not ids:
ids = self.search(cr, uid, domain, context=context, limit=limit)
# FP Todo: flatten to max X level of mail_thread
messages = self.browse(cr, uid, ids, context=context)
result = []
@@ -253,9 +240,8 @@ class mail_message(osv.Model):
break
# Flatten the result
# if thread_level > 0:
# result = self.message_read_tree_flatten(cr, uid, result, 0, thread_level, context=context)
if thread_level > 0:
result = self.message_read_tree_flatten(cr, uid, result, 0, thread_level, context=context)
return result
#------------------------------------------------------
+5 -18
View File
@@ -208,10 +208,11 @@ class mail_thread(osv.Model):
# mail.message wrappers and tools
#------------------------------------------------------
def _needaction_domain_get(self, cr, uid, context={}):
def _needaction_domain_get(self, cr, uid, context=None):
if self._needaction:
return [('message_unread', '=', True)]
return []
#------------------------------------------------------
# Mail gateway
#------------------------------------------------------
@@ -460,29 +461,15 @@ class mail_thread(osv.Model):
continue
# 2) text/plain -> <pre/>
if part.get_content_type() == 'text/plain' and (not alternative or not body):
insertion_point = body.find('</html>')
# plain text is wrapped in <pre/> to preserve formatting
text = u'<pre>%s</pre>' % tools.ustr(part.get_payload(decode=True),
encoding, errors='replace')
if insertion_point != -1:
body = body[:insertion_point] + text + body[insertion_point:]
else:
body += text
body = tools.append_content_to_html(body, tools.ustr(part.get_payload(decode=True),
encoding, errors='replace'))
# 3) text/html -> raw
elif part.get_content_type() == 'text/html':
html = tools.ustr(part.get_payload(decode=True), encoding, errors='replace')
if alternative:
body = html
# force </html> tag to lowercase, for easier matching
body = re.sub(r'(?i)</html>', r'</html>', body)
else:
# strip enclosing html/body tags and append to existing body
html = re.sub(r'(?i)(</?html>|</?body>)', '', html)
insertion_point = body.find('</html>')
if insertion_point != -1:
body = body[:insertion_point] + html + body[insertion_point:]
else:
body += html
body = tools.append_content_to_html(body, html, plaintext=False)
# 4) Anything else -> attachment
else:
attachments.append((filename or 'attachment', part.get_payload(decode=True)))
+83 -79
View File
@@ -19,9 +19,11 @@
#
##############################################################################
from openerp.tests import common
import base64
import tools
from openerp.tests import common
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
To: {to}
Received: by mail1.openerp.com (Postfix, from userid 10002)
@@ -246,10 +248,10 @@ class test_mail(common.TransactionCase):
_body2 = '<html>Pigs rules</html>'
_mail_body2 = '<html>Pigs rules\n<pre>Admin</pre>\n</html>'
_mail_bodyalt2 = 'Pigs rules\nAdmin\n'
_attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
# Post comment with body and subject, comment preference
# CASE1: post comment, body and subject specified
msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, msg_type='comment')
# Fetch: created mail_message, mail_mail, sent_email
message = self.mail_message.browse(cr, uid, msg_id)
mail_ids = self.mail_mail.search(cr, uid, [], limit=1)
mail = self.mail_mail.browse(cr, uid, mail_ids[0])
@@ -262,7 +264,7 @@ class test_mail(common.TransactionCase):
self.assertEqual(mail.subject, _subject, 'mail.mail subject incorrect')
self.assertEqual(mail.body_html, _mail_body1, 'mail.mail body_html incorrect')
self.assertEqual(mail.mail_message_id.id, msg_id, 'mail_mail.mail_message_id is not the id of its related mail_message)')
# Test: sent_email: email send by server: correct subject and body
# Test: sent_email: email send by server: correct subject, body; body_alternative
self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
self.assertEqual(sent_email['body'], _mail_body1, 'sent_email body incorrect')
self.assertEqual(sent_email['body_alternative'], _mail_bodyalt1, 'sent_email body_alternative is incorrect')
@@ -277,23 +279,24 @@ class test_mail(common.TransactionCase):
# Test: sent_email: email_to should contain b@b, not c@c (pref email), not a@a (writer)
self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
# New post: test automatic subject, signature in html, add a partner, email preference, parent_id previous message
msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, msg_type='email', partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id)
# Fetch: created mail_message, mail_mail, sent_email
# CASE2: post an email with attachments, parent_id, partner_ids
# TESTS: automatic subject, signature in body_html, attachments propagation
msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, msg_type='email',
partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments)
message = self.mail_message.browse(cr, uid, msg_id2)
mail_ids = self.mail_mail.search(cr, uid, [], limit=1)
mail = self.mail_mail.browse(cr, uid, mail_ids[0])
sent_email = self._build_email_kwargs
# Test: mail_message: subject is False, body is _body1 (no formatting done), parent_id is msg_id
# Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id
self.assertEqual(message.subject, False, 'mail.message subject incorrect')
self.assertEqual(message.body, _body2, 'mail.message body incorrect')
self.assertEqual(message.parent_id.id, msg_id, 'mail.message parent_id incorrect')
# Test: mail_mail: subject is False, body_html is _mail_body1 (signature appended)
# Test: mail_mail: subject is False, body_html is _mail_body2 (signature appended)
self.assertEqual(mail.subject, False, 'mail.mail subject is incorrect')
self.assertEqual(mail.body_html, _mail_body2, 'mail.mail body_html incorrect')
self.assertEqual(mail.mail_message_id.id, msg_id2, 'mail_mail.mail_message_id incorrect')
# Test: sent_email: email send by server: correct subject and body
# Test: sent_email: email send by server: correct subject, body, body_alternative
self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
self.assertEqual(sent_email['body'], _mail_body2, 'sent_email body incorrect')
self.assertEqual(sent_email['body_alternative'], _mail_bodyalt2, 'sent_email body_alternative incorrect')
@@ -301,17 +304,20 @@ class test_mail(common.TransactionCase):
message_pids = set([partner.id for partner in message.partner_ids])
test_pids = set([p_a_id, p_b_id, p_c_id, p_d_id])
self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect')
# Test: notification linked to this message = group followers = partner_ids
# Test: notifications linked to this message = group followers = partner_ids
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
# Test: sent_email: email_to should contain b@b, c@c, not a@a (writer)
self.assertEqual(set(sent_email['email_to']), set(['b@b', 'c@c']), 'sent_email email_to incorrect')
# Test: attachments
for i in range(len(message.attachment_ids)):
self.assertEqual(message.attachment_ids[i].name, _attachments[i][0], 'mail.message attachment name incorrect')
self.assertEqual(message.attachment_ids[i].res_model, 'mail.group', 'mail.message attachment res_model incorrect')
self.assertEqual(message.attachment_ids[i].res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
self.assertEqual(base64.b64decode(message.attachment_ids[i].datas), _attachments[i][1], 'mail.message attachment data incorrect')
def test_21_message_post_attachments(self):
""" Tests designed for attachments. """
def test_22_message_compose_wizard(self):
def test_21_message_compose_wizard(self):
""" Tests designed for the mail.compose.message wizard. """
cr, uid = self.cr, self.uid
mail_compose = self.registry('mail.compose.message')
@@ -321,11 +327,16 @@ class test_mail(common.TransactionCase):
# Mail data
_subject = 'Pigs'
_subject_reply = 'Re: Pigs'
_mail_subject = '%s posted on %s' % (user_admin.name, group_pigs.name)
_body_text = 'Pigs rules'
_msg_body1 = '<pre>Pigs rules</pre>'
_body_html = '<html>Pigs rules</html>'
_msg_body2 = '<html>Pigs rules</html>'
_attachments = [
{'name': 'First', 'datas': base64.b64encode('My first attachment')},
{'name': 'Second', 'datas': base64.b64encode('My second attachment')}
]
# Create partners
# 0 - Admin
@@ -340,46 +351,59 @@ class test_mail(common.TransactionCase):
# Subscribe #1
group_pigs.message_subscribe([p_b_id])
# Comment group_pigs: body_text
# CASE1: comment group_pigs with body_text and subject
compose_id = mail_compose.create(cr, uid,
{'subject': _subject, 'body_text': _body_text, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
{'mail.compose.message.mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id})
compose = mail_compose.browse(cr, uid, compose_id)
# Test: model, res_id
self.assertTrue(compose.model == 'mail.group' and compose.res_id == self.group_pigs_id,
'mail.compose.message has model %s and res_id %s; should be mail.group and %s' % (compose.model, compose.res_id, self.group_pigs_id))
# Test: mail.compose.message: model, res_id
self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
# Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id])
group_pigs.refresh()
msg = group_pigs.message_ids[0]
message = group_pigs.message_ids[0]
# Test: mail.message: subject, body inside pre
self.assertTrue(msg.subject == False and msg.body == _msg_body1,
'mail.message subject is %s, body is %s; should be %s and %s' % (msg.subject, msg.body, False, _msg_body1))
# Test: mail.message partners = notified people: group_pigs followers (a, b) + mail.compose.message partner_ids (c, d)
msg_pids = [partner.id for partner in msg.partner_ids]
self.assertEqual(message.subject, False, 'mail.message incorrect subject')
self.assertEqual(message.body, _msg_body1, 'mail.message incorrect body')
# Test: mail.message: partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
msg_pids = [partner.id for partner in message.partner_ids]
test_pids = [p_a_id, p_b_id, p_c_id, p_d_id]
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg.id)])
self.assertTrue(len(msg_pids) == 4, 'There are %s partners linked to the newly posted comment; should be 4' % (len(msg_pids)))
self.assertTrue(len(notif_ids) == 4, 'There are %s entries in mail_notification: should be 4' % (len(notif_ids)))
self.assertTrue(all(id in msg_pids for id in test_pids) and len(msg_pids) == len(test_pids),
'Admin, Bert Raoul and Roger should be the 4 partners of the newly created message')
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
self.assertEqual(len(notif_ids), 4, 'mail.message: too much notifications created')
self.assertEqual(set(msg_pids), set(test_pids), 'mail.message partner_ids incorrect')
# Create a reply to the last comment
# CASE2: reply to last comment (update its subject) with attachments
message.write({'subject': _subject})
compose_id = mail_compose.create(cr, uid,
{}, {'mail.compose.message.mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id,
'active_id': msg.id})
{'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
{'mail.compose.message.mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'active_id': message.id})
compose = mail_compose.browse(cr, uid, compose_id)
# Test: model, res_id, parent_id
self.assertTrue(compose.model == 'mail.group' and compose.res_id == self.group_pigs_id,
'Wizard message has model: %s and res_id:%s; should be mail.group and %s' % (compose.model, compose.res_id, self.group_pigs_id))
self.assertEqual(compose.parent_id.id, msg.id,
'Wizard parent_id is %d; should be %d' % (compose.parent_id.id, msg.id))
# Test: form view methods
# Test: model, res_id, parent_id, content_subtype
self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
# 3 - Create in mass_mail composition mode that should work with or without email_template installed
# Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id])
group_pigs.refresh()
message = group_pigs.message_ids[0]
# Test: subject as Re:.., body in html
self.assertEqual(message.subject, _subject_reply, 'mail.message incorrect subject')
self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote></div>', message.body, 'mail.message body is incorrect')
# Test: attachments
for i in range(len(message.attachment_ids)):
self.assertEqual(message.attachment_ids[i].name, _attachments[i]['name'], 'mail.message attachment name incorrect')
self.assertEqual(message.attachment_ids[i].res_model, 'mail.group', 'mail.message attachment res_model incorrect')
self.assertEqual(message.attachment_ids[i].res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
self.assertEqual(base64.b64decode(message.attachment_ids[i].datas), base64.b64decode(_attachments[i]['datas']), 'mail.message attachment data incorrect')
# CASE3 - Create in mass_mail composition mode that should work with or without email_template installed
compose_id = mail_compose.create(cr, uid,
{'subject': _subject, 'body': '${object.description}'},
{'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': -1,
@@ -389,27 +413,16 @@ class test_mail(common.TransactionCase):
# Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id]})
group_pigs.refresh()
msg = group_pigs.message_ids[0]
message = group_pigs.message_ids[0]
# Test: last message on Pigs = last created message
test_msg = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [], limit=1))[0]
self.assertEqual(msg.id, test_msg.id, 'Pigs did not receive its mass mailing message')
self.assertEqual(message.id, test_msg.id, 'Pigs did not receive its mass mailing message')
# Test: mail.message: subject, body
self.assertEqual(msg.subject, _subject, 'mail.message subject is incorrect')
self.assertEqual(msg.body, group_pigs.description, 'mail.message body is incorrect')
self.assertEqual(message.subject, _subject, 'mail.message subject is incorrect')
self.assertEqual(message.body, group_pigs.description, 'mail.message body is incorrect')
def test_30_message_read(self):
""" Tests designed for message_read. """
def _simplify_struct(read_dict):
res = []
for val in read_dict:
current = {'_id': val['id']}
if val.get('child_ids'):
current['child_ids'] = _simplify_struct(val.get('child_ids'))
res.append(current)
return res
# TDE NOTE: this test is not finished, as the message_read method is not fully specified.
# It wil be updated as soon as we have fixed specs !
cr, uid = self.cr, self.uid
@@ -417,38 +430,31 @@ class test_mail(common.TransactionCase):
# Test message_read_tree_flatten that flattens a thread according to a given thread_level
import copy
tree = [
{'id': 1, 'child_ids':[
{'id': 3, 'child_ids': [] },
{'id': 4, 'child_ids': [
{'id': 5, 'child_ids': []},
{'id': 12, 'child_ids': []},
tree = [{'id': 1, 'child_ids':[
{'id': 3, 'child_ids': [] },
{'id': 4, 'child_ids': [
{'id': 5, 'child_ids': []},
{'id': 12, 'child_ids': []},
] },
{'id': 8, 'child_ids': [
{'id': 10, 'child_ids': []},
] },
] },
{'id': 8, 'child_ids': [
{'id': 10, 'child_ids': []},
{'id': 2, 'child_ids': [
{'id': 7, 'child_ids': [
{'id': 9, 'child_ids': []},
] },
] },
] },
{'id': 2, 'child_ids': [
{'id': 7, 'child_ids': [
{'id': 9, 'child_ids': []},
{'id': 6, 'child_ids': [
{'id': 11, 'child_ids': [] },
] },
] },
{'id': 6, 'child_ids': [
{'id': 11, 'child_ids': [] },
] },
]
]
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 0)
# self.mail_message._debug_print_tree(new_tree)
# print '-------------------'
self.assertTrue(len(new_tree) == 12, 'Flattening wrongly produced')
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 1)
# self.mail_message._debug_print_tree(new_tree)
# print '-------------------'
self.assertTrue(len(new_tree) == 3 and len(new_tree[0]['child_ids']) == 6 and len(new_tree[1]['child_ids']) == 2 and len(new_tree[2]['child_ids']) == 1,
'Flattening wrongly produced')
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 2)
# self.mail_message._debug_print_tree(new_tree)
# print '-------------------'
self.assertTrue(len(new_tree) == 3 and len(new_tree[0]['child_ids']) == 3 and len(new_tree[0]['child_ids'][1]) == 2,
'Flattening wrongly produced')
@@ -468,12 +474,10 @@ class test_mail(common.TransactionCase):
# Second try: read with thread_level 1
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=1)
# self.mail_message._debug_print_tree(tree)
self.assertTrue(len(tree) == 2 and len(tree[1]['child_ids']) == 3, 'Incorrect number of child in message_read')
# Third try: read with thread_level 2
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=2)
# self.mail_message._debug_print_tree(tree)
self.assertTrue(len(tree) == 2 and len(tree[1]['child_ids']) == 2 and len(tree[1]['child_ids'][0]['child_ids']) == 1, 'Incorrect number of child in message_read')
def test_40_needaction(self):
+34 -44
View File
@@ -19,6 +19,7 @@
#
##############################################################################
import base64
import re
import tools
@@ -71,12 +72,12 @@ class mail_compose_message(osv.TransientModel):
composition_mode = context.get('default_composition_mode', context.get('mail.compose.message.mode'))
model = context.get('default_model', context.get('active_model'))
res_id = context.get('default_res_id', context.get('active_id'))
active_id = context.get('active_id')
message_id = context.get('default_parent_id', context.get('message_id', context.get('active_id')))
active_ids = context.get('active_ids')
# get default values according to the composition mode
if composition_mode == 'reply':
vals = self.get_message_data(cr, uid, active_id, context=context)
vals = self.get_message_data(cr, uid, message_id, context=context)
elif composition_mode == 'comment' and model and res_id:
vals = self.get_record_data(cr, uid, model, res_id, context=context)
elif composition_mode == 'mass_mail' and model and active_ids:
@@ -154,7 +155,7 @@ class mail_compose_message(osv.TransientModel):
message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context=context)
# create subject
re_prefix = _("Re:")
re_prefix = _('Re:')
reply_subject = tools.ustr(message_data.subject or '')
if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)):
reply_subject = "%s %s" % (re_prefix, reply_subject)
@@ -162,7 +163,7 @@ class mail_compose_message(osv.TransientModel):
reply_header = _('On %(date)s, %(sender_name)s wrote:') % {
'date': message_data.date if message_data.date else '',
'sender_name': message_data.author_id.name }
reply_body = '<div>%s<blockquote>%s</blockquote></div>%s' % (reply_header, message_data.body, current_user.signature)
reply_body = '<div>%s<blockquote>%s</blockquote></div>' % (reply_header, message_data.body)
# get partner_ids from original message
partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else []
@@ -223,6 +224,22 @@ class mail_compose_message(osv.TransientModel):
res.update(self._verify_partner_email(cr, uid, value[0][2], context=context))
return res
def unlink(self, cr, uid, ids, context=None):
# Cascade delete all attachments, as they are owned by the composition wizard
for wizard in self.read(cr, uid, ids, ['attachment_ids'], context=context):
self.pool.get('ir.attachment').unlink(cr, uid, wizard['attachment_ids'], context=context)
return super(mail_compose_message,self).unlink(cr, uid, ids, context=context)
def dummy(self, cr, uid, ids, context=None):
""" TDE: defined to have buttons that do basically nothing. It is
currently impossible to have buttons that do nothing special
in views (if type not specified, considered as 'object'). """
return True
#------------------------------------------------------
# Wizard validation and send
#------------------------------------------------------
def send_mail(self, cr, uid, ids, context=None):
""" Process the wizard content and proceed with sending the related
email(s), rendering any template patterns on the fly if needed. """
@@ -242,19 +259,18 @@ class mail_compose_message(osv.TransientModel):
'subject': wizard.subject if wizard.content_subtype == 'html' else False,
'body': wizard.body if wizard.content_subtype == 'html' else '<pre>%s</pre>' % tools.ustr(wizard.body_text),
'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
'attachments': [(attach.datas_fname, attach.datas) for attach in wizard.attachment_ids],
'attachments': [(attach.name or attach.datas_fname, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
}
# mass mailing: render and override default values
if mass_mail_mode and wizard.model:
email_dict = self.render_message(cr, uid, wizard, wizard.model, res_id, context=context)
post_values['subject'] = email_dict.get('subject', False)
post_values['body'] = email_dict.get('body', '')
if email_dict.get('partner_ids'):
post_values['partner_ids'] += [(4, id) for id in email_dict.get('partner_ids')]
if email_dict.get('attachments'):
post_values['attachments'] += [(attach.datas_fname, attach.datas) for attach in email_dict.get('attachments')]
new_partner_ids = email_dict.pop('partner_ids', [])
post_values['partner_ids'] += new_partner_ids
new_attachments = email_dict.pop('attachments', [])
post_values['attachments'] += new_attachments
post_values.update(email_dict)
# post the message
active_model_pool.message_post(cr, uid, res_ids, msg_type='comment', context=context, **post_values)
active_model_pool.message_post(cr, uid, [res_id], msg_type='comment', context=context, **post_values)
return {'type': 'ir.actions.act_window_close'}
@@ -263,20 +279,9 @@ class mail_compose_message(osv.TransientModel):
This method is meant to be inherited by email_template that will
produce a more complete dictionary, with email_to, ...
"""
# TDE: TODO: to move into mail.compose.message in email template
# mails = tools.email_split(email_dict.get('email_to', '')) + tools.email_split(email_dict.get('email_cc', ''))
# for mail in mails:
# partner_search_ids = self.pool.get('res.partner').search(cr, uid, [('email', 'ilike', email)], context=context)
# if partner_search_ids:
# partner_ids.append((4, 0, partner_search_ids[0]))
# else:
# partner_id = self.pool.get('res.partner').name_create(cr, uid, values['email_to'], context=context)
# partner_ids.append((4, 0, partner_id))
return {
'subject': self.render_template(cr, uid, wizard.subject, model, res_id, context),
'body': self.render_template(cr, uid, wizard.body, model, res_id, context),
'partner_ids': [],
'attachment_ids': [],
}
def render_template(self, cr, uid, template, model, res_id, context=None):
@@ -297,25 +302,10 @@ class mail_compose_message(osv.TransientModel):
context = {}
def merge(match):
exp = str(match.group()[2:-1]).strip()
result = eval(exp,
{
'user' : self.pool.get('res.users').browse(cr, uid, uid, context=context),
'object' : self.pool.get(model).browse(cr, uid, res_id, context=context),
'context': dict(context), # copy context to prevent side-effects of eval
})
if result in (None, False):
return ""
return tools.ustr(result)
result = eval(exp, {
'user' : self.pool.get('res.users').browse(cr, uid, uid, context=context),
'object' : self.pool.get(model).browse(cr, uid, res_id, context=context),
'context': dict(context), # copy context to prevent side-effects of eval
})
return result and tools.ustr(result) or ''
return template and EXPRESSION_PATTERN.sub(merge, template)
def unlink(self, cr, uid, ids, context=None):
# Cascade delete all attachments, as they are owned by the composition wizard
for wizard in self.read(cr, uid, ids, ['attachment_ids'], context=context):
self.pool.get('ir.attachment').unlink(cr, uid, wizard['attachment_ids'], context=context)
return super(mail_compose_message,self).unlink(cr, uid, ids, context=context)
def dummy(self, cr, uid, ids, context=None):
""" TDE: defined to have buttons that do basically nothing. It is
currently impossible to have buttons that do nothing special
in views (if type not specified, considered as 'object'). """
return True
+5 -5
View File
@@ -1047,27 +1047,27 @@ class mrp_production(osv.osv):
# ---------------------------------------------------
def create_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Manufacturing order has been <b>created</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Manufacturing order has been <b>created</b>."), subtype="new", context=context)
return True
def action_cancel_send_note(self, cr, uid, ids, context=None):
message = _("Manufacturing order has been <b>canceled</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="cancelled", context=context)
return True
def action_ready_send_note(self, cr, uid, ids, context=None):
message = _("Manufacturing order is <b>ready to produce</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="ready", context=context)
return True
def action_in_production_send_note(self, cr, uid, ids, context=None):
message = _("Manufacturing order is <b>in production</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="production", context=context)
return True
def action_done_send_note(self, cr, uid, ids, context=None):
message = _("Manufacturing order has been <b>done</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="closed", context=context)
return True
def action_confirm_send_note(self, cr, uid, ids, context=None):
+33
View File
@@ -27,5 +27,38 @@ From the Manufacturing Settings, you can choose to compute production schedules
<field name="number_increment">1</field>
</record>
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="name">new</field>
<field name="model_ids" eval="[(4,ref('mrp.model_mrp_production'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_ready" model="mail.message.subtype">
<field name="name">ready</field>
<field name="model_ids" eval="[(4,ref('mrp.model_mrp_production'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_production" model="mail.message.subtype">
<field name="name">production</field>
<field name="model_ids" eval="[(4,ref('mrp.model_mrp_production'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('mrp.model_mrp_production'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_closed" model="mail.message.subtype">
<field name="name">closed</field>
<field name="model_ids" eval="[(4,ref('mrp.model_mrp_production'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('mrp.model_mrp_production'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('mrp.model_mrp_production'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('mrp.model_mrp_production'))]"/>
</record>
</data>
</openerp>
@@ -30,5 +30,39 @@
<field name="start_stop">done</field>
</record>
<!-- SUBTYPE -->
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="name">new</field>
<field name="model_ids" eval="[(4,ref('mrp_operations.model_mrp_production_workcenter_line'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_started" model="mail.message.subtype">
<field name="name">started</field>
<field name="model_ids" eval="[(4,ref('mrp_operations.model_mrp_production_workcenter_line'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_pending" model="mail.message.subtype">
<field name="name">pending</field>
<field name="model_ids" eval="[(4,ref('mrp_operations.model_mrp_production_workcenter_line'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('mrp_operations.model_mrp_production_workcenter_line'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_closed" model="mail.message.subtype">
<field name="name">closed</field>
<field name="model_ids" eval="[(4,ref('mrp_operations.model_mrp_production_workcenter_line'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('mrp_operations.model_mrp_production_workcenter_line'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('mrp_operations.model_mrp_production_workcenter_line'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('mrp_operations.model_mrp_production_workcenter_line'))]"/>
</record>
</data>
</openerp>
+5 -5
View File
@@ -224,7 +224,7 @@ class mrp_production_workcenter_line(osv.osv):
for workorder in self.browse(cr, uid, ids):
for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
message = _("Work order has been <b>created</b> for production order <em>%s</em>.") % (prod.id.name)
self.message_post(cr, uid, [workorder.id], body=message, context=context)
self.message_post(cr, uid, [workorder.id], body=message, subtype="new", context=context)
return True
def action_start_send_note(self, cr, uid, ids, context=None):
@@ -232,7 +232,7 @@ class mrp_production_workcenter_line(osv.osv):
for workorder in self.browse(cr, uid, ids):
for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
message = _("Work order has been <b>started</b> for production order <em>%s</em>.") % (prod.id.name)
self.message_post(cr, uid, [workorder.id], body=message, context=context)
self.message_post(cr, uid, [workorder.id], body=message, subtype="started", context=context)
return True
def action_done_send_note(self, cr, uid, ids, context=None):
@@ -240,7 +240,7 @@ class mrp_production_workcenter_line(osv.osv):
for workorder in self.browse(cr, uid, ids):
for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
message = _("Work order has been <b>done</b> for production order <em>%s</em>.") % (prod.id.name)
self.message_post(cr, uid, [workorder.id], body=message, context=context)
self.message_post(cr, uid, [workorder.id], body=message, subtype="closed", context=context)
return True
def action_pending_send_note(self, cr, uid, ids, context=None):
@@ -248,7 +248,7 @@ class mrp_production_workcenter_line(osv.osv):
for workorder in self.browse(cr, uid, ids):
for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
message = _("Work order is <b>pending</b> for production order <em>%s</em>.") % (prod.id.name)
self.message_post(cr, uid, [workorder.id], body=message, context=context)
self.message_post(cr, uid, [workorder.id], body=message, subtype="pending", context=context)
return True
def action_cancel_send_note(self, cr, uid, ids, context=None):
@@ -256,7 +256,7 @@ class mrp_production_workcenter_line(osv.osv):
for workorder in self.browse(cr, uid, ids):
for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
message = _("Work order has been <b>cancelled</b> for production order <em>%s</em>.") % (prod.id.name)
self.message_post(cr, uid, [workorder.id], body=message, context=context)
self.message_post(cr, uid, [workorder.id], body=message, subtype="cancelled", context=context)
return True
mrp_production_workcenter_line()
+1
View File
@@ -43,6 +43,7 @@ The following topics should be covered by this module:
'data': [
'security/ir.model.access.csv',
'security/mrp_repair_security.xml',
'mrp_repair_data.xml',
'mrp_repair_sequence.xml',
'wizard/mrp_repair_cancel_view.xml',
'wizard/mrp_repair_make_invoice_view.xml',
+7 -7
View File
@@ -571,40 +571,40 @@ class mrp_repair(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
for repair in self.browse(cr, uid, ids, context):
message = _("Repair Order for <em>%s</em> has been <b>created</b>." % (repair.product_id.name))
self.message_post(cr, uid, [repair.id], body=message, context=context)
self.message_post(cr, uid, [repair.id], body=message, subtype="new", 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 <em>%s</em> has been <b>started</b>." % (repair.product_id.name))
self.message_post(cr, uid, [repair.id], body=message, context=context)
self.message_post(cr, uid, [repair.id], body=message, subtype="started", 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 <b>waiting for validation</b>.") % (repair.invoice_id.amount_total, repair.invoice_id.currency_id.symbol)
self.message_post(cr, uid, [repair.id], body=message, context=context)
self.message_post(cr, uid, [repair.id], body=message, subtype="pending", 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 <em>%s</em> has been <b>accepted</b>." % (repair.product_id.name))
self.message_post(cr, uid, [repair.id], body=message, context=context)
self.message_post(cr, uid, [repair.id], body=message, subtype="accepted", context=context)
return True
def set_cancel_send_note(self, cr, uid, ids, context=None):
message = _("Repair has been <b>cancelled</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="cancelled", context=context)
return True
def set_ready_send_note(self, cr, uid, ids, context=None):
message = _("Repair Order is now <b>ready</b> to repair.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="ready", context=context)
return True
def set_done_send_note(self, cr, uid, ids, context=None):
message = _("Repair Order is <b>closed</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
self.message_post(cr, uid, ids, body=message, subtype="closed", context=context)
return True
mrp_repair()
+48
View File
@@ -0,0 +1,48 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="name">new</field>
<field name="model_ids" eval="[(4,ref('mrp_repair.model_mrp_repair'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_started" model="mail.message.subtype">
<field name="name">started</field>
<field name="model_ids" eval="[(4,ref('mrp_repair.model_mrp_repair'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_ready" model="mail.message.subtype">
<field name="name">ready</field>
<field name="model_ids" eval="[(4,ref('mrp_repair.model_mrp_repair'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_pending" model="mail.message.subtype">
<field name="name">pending</field>
<field name="model_ids" eval="[(4,ref('mrp_repair.model_mrp_repair'))]"/>
</record>
<record id="mail.mail_subtype_accepted" model="mail.message.subtype">
<field name="name">accepted</field>
<field name="model_ids" eval="[(4,ref('mrp_repair.model_mrp_repair'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('mrp_repair.model_mrp_repair'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_closed" model="mail.message.subtype">
<field name="name">closed</field>
<field name="model_ids" eval="[(4,ref('mrp_repair.model_mrp_repair'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('mrp_repair.model_mrp_repair'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('mrp_repair.model_mrp_repair'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('mrp_repair.model_mrp_repair'))]"/>
</record>
</data>
</openerp>
File diff suppressed because it is too large Load Diff
@@ -15,6 +15,10 @@ class pos_session_opening(osv.osv_memory):
'pos_state' : fields.selection(pos_session.POS_SESSION_STATE,
'Session State', readonly=True),
'show_config' : fields.boolean('Show Config', readonly=True),
'pos_session_name' : fields.related('pos_session_id', 'name',
type='char', size=64, readonly=True),
'pos_session_username' : fields.related('pos_session_id', 'user_id', 'name',
type='char', size=64, readonly=True)
}
def open_ui(self, cr, uid, ids, context=None):
@@ -66,7 +70,9 @@ class pos_session_opening(osv.osv_memory):
def on_change_config(self, cr, uid, ids, config_id, context=None):
result = {
'pos_session_id': False,
'pos_state': False
'pos_state': False,
'pos_session_username' : False,
'pos_session_name' : False,
}
if not config_id:
return {'value': result}
@@ -76,8 +82,11 @@ class pos_session_opening(osv.osv_memory):
('config_id', '=', config_id),
], context=context)
if session_ids:
result['pos_state'] = proxy.browse(cr, uid, session_ids[0], context=context).state
result['pos_session_id'] = session_ids[0]
session = proxy.browse(cr, uid, session_ids[0], context=context)
result['pos_state'] = session.state
result['pos_session_id'] = session.id
result['pos_session_name'] = session.name
result['pos_session_username'] = session.user_id.name
return {'value' : result}
def default_get(self, cr, uid, fieldnames, context=None):
@@ -44,6 +44,15 @@
interface.
</p>
</div>
<div attrs="{'invisible' : [('pos_state', '!=', 'opened')]}" class="oe_view_nocontent">
<p class="oe_view_nocontent_create">
Click to continue the session.
</p>
<p>
The session <field name="pos_session_name" class="oe_inline" /> (<field name="pos_session_username" class="oe_inline" />) is "<b><field name="pos_state" class="oe_inline" /></b>"<br/>
You can continue sales from the touchscreen interface by clicking on "<b>Settings</b>" or close the cash register session ?
</p>
</div>
</form>
</field>
+6 -6
View File
@@ -496,22 +496,22 @@ class procurement_order(osv.osv):
return obj_id
def create_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been <b>created</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Procurement has been <b>created</b>."), subtype="new", context=context)
def confirm_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been <b>confirmed</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Procurement has been <b>confirmed</b>."), subtype="confirmed", context=context)
def running_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been set to <b>running</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Procurement has been set to <b>running</b>."), subtype="running", context=context)
def ready_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been set to <b>ready</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Procurement has been set to <b>ready</b>."), subtype="ready", context=context)
def cancel_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been <b>cancelled</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Procurement has been <b>cancelled</b>."), subtype="cancelled", context=context)
def done_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been <b>done</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Procurement has been <b>done</b>."), subtype="closed", context=context)
procurement_order()
+35
View File
@@ -28,5 +28,40 @@
<field name="number_next">1</field>
<field name="number_increment">1</field>
</record>
<!--
Mail: mail.message.subtype
-->
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('procurement.model_procurement_order'))]"/>
</record>
<record id="mail.mail_subtype_confirmed" model="mail.message.subtype">
<field name="name">confirmed</field>
<field name="model_ids" eval="[(4,ref('procurement.model_procurement_order'))]"/>
</record>
<record id="mail.mail_subtype_ready" model="mail.message.subtype">
<field name="name">ready</field>
<field name="model_ids" eval="[(4,ref('procurement.model_procurement_order'))]"/>
</record>
<record id="mail.mail_subtype_running" model="mail.message.subtype">
<field name="name">running</field>
<field name="model_ids" eval="[(4,ref('procurement.model_procurement_order'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('procurement.model_procurement_order'))]"/>
</record>
<record id="mail.mail_subtype_closed" model="mail.message.subtype">
<field name="name">closed</field>
<field name="model_ids" eval="[(4,ref('procurement.model_procurement_order'))]"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('procurement.model_procurement_order'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('procurement.model_procurement_order'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('procurement.model_procurement_order'))]"/>
</record>
</data>
</openerp>
+2 -1
View File
@@ -26,6 +26,7 @@ from osv import fields, osv
from openerp.addons.resource.faces import task as Task
import time
from tools.translate import _
from openerp import SUPERUSER_ID
_TASK_STATE = [('draft', 'New'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done'), ('cancelled', 'Cancelled')]
@@ -853,7 +854,7 @@ class task(base_stage, osv.osv):
if context is None: context = {}
# read uom as admin to avoid access rights issues, e.g. for portal/share users,
# this should be safe (no context passed to avoid side-effects)
obj_tm = users_obj.browse(cr, 1, uid, context=context).company_id.project_time_mode_id
obj_tm = users_obj.browse(cr, SUPERUSER_ID, uid, context=context).company_id.project_time_mode_id
tm = obj_tm and obj_tm.name or 'Hours'
res = super(task, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu=submenu)
+5 -5
View File
@@ -735,7 +735,7 @@ class purchase_order(osv.osv):
return [('state','=','draft')]
def create_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Request for quotation <b>created</b>."), context=context)
return self.message_post(cr, uid, ids, body=_("Request for quotation <b>created</b>."), subtype="new", context=context)
def confirm_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
@@ -753,20 +753,20 @@ class purchase_order(osv.osv):
def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
for order in self.browse(cr, uid, ids, context=context):
for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), subtype="pending", context=context)
def shipment_done_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("""Shipment <b>received</b>."""), context=context)
self.message_post(cr, uid, ids, body=_("""Shipment <b>received</b>."""), subtype="received", context=context)
def invoice_done_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), subtype="paid", context=context)
def draft_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Purchase Order has been set to <b>draft</b>."), context=context)
def cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
self.message_post(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), subtype="cancelled", context=context)
purchase_order()
+32
View File
@@ -49,5 +49,37 @@ You can also manage purchase requisitions, see also the Purchase Settings.</fiel
model="ir.values"
name="set"/>
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="name">new</field>
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_paid" model="mail.message.subtype">
<field name="name">paid</field>
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
</record>
<record id="mail.mail_subtype_pending" model="mail.message.subtype">
<field name="name">pending</field>
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_received" model="mail.message.subtype">
<field name="name">received</field>
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase.model_purchase_order'))]"/>
</record>
</data>
</openerp>
@@ -94,13 +94,13 @@ class purchase_requisition(osv.osv):
self.message_post(cr, uid, ids, body=_("Draft Requisition has been <b>sent to suppliers</b>."), context=context)
def reset_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been set to <b>draft</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been set to <b>draft</b>."), subtype="new", context=context)
def done_to_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>done</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>done</b>."), subtype="closed", context=context)
def cancel_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>cancelled</b>."), context=context)
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>cancelled</b>."), subtype="cancelled", context=context)
def _planned_date(self, requisition, delay=0.0):
company = requisition.company_id
@@ -184,7 +184,7 @@ class purchase_requisition(osv.osv):
return res
def create_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>created</b>."), context=context)
return self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>created</b>."), subtype="new", context=context)
def create(self, cr, uid, vals, context=None):
requisition = super(purchase_requisition, self).create(cr, uid, vals, context=context)
@@ -1,10 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<function
<function
eval="('default',False,'warehouse_id', [('purchase.requisition', False)], ref('stock.warehouse0'), True, False, False, False, True)"
id="purchase_default_set"
model="ir.values"
name="set"/>
<!-- SUBTYPE -->
<record id="mail.mail_subtype_closed" model="mail.message.subtype">
<field name="name">closed</field>
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('purchase_requisition.model_purchase_requisition'))]"/>
</record>
</data>
</openerp>
+1 -1
View File
@@ -55,7 +55,7 @@ If you need to manage your sales pipeline (leads, opportunities, phonecalls), yo
<field name="model_ids" eval="[(4,ref('sale.model_sale_order'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_cancel" model="mail.message.subtype">
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="name">cancelled</field>
<field name="model_ids" eval="[(4,ref('sale.model_sale_order'))]"/>
<field name="default" eval="False"/>
+10 -10
View File
@@ -7,30 +7,30 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:37+0000\n"
"PO-Revision-Date: 2012-08-18 17:40+0000\n"
"Last-Translator: Heling Yao <Unknown>\n"
"PO-Revision-Date: 2012-08-31 14:59+0000\n"
"Last-Translator: ccdos <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-08-28 06:07+0000\n"
"X-Generator: Launchpad (build 15864)\n"
"X-Launchpad-Export-Date: 2012-09-01 04:55+0000\n"
"X-Generator: Launchpad (build 15890)\n"
#. module: sale_crm
#: field:sale.order,categ_id:0
msgid "Category"
msgstr ""
msgstr "分类"
#. module: sale_crm
#: sql_constraint:sale.order:0
msgid "Order Reference must be unique per Company!"
msgstr ""
msgstr "订单号必须在一个公司范围内唯一"
#. module: sale_crm
#: code:addons/sale_crm/wizard/crm_make_sale.py:112
#, python-format
msgid "Converted to Sales Quotation(%s)."
msgstr ""
msgstr "转换为销售报价单(%s)。"
#. module: sale_crm
#: view:crm.make.sale:0
@@ -73,7 +73,7 @@ msgstr "在生成销售订单后检查是否关闭商机。"
#. module: sale_crm
#: view:board.board:0
msgid "My Opportunities"
msgstr ""
msgstr "我的商机"
#. module: sale_crm
#: view:crm.lead:0
@@ -104,13 +104,13 @@ msgstr "关闭商机"
#. module: sale_crm
#: view:board.board:0
msgid "My Planned Revenues by Stage"
msgstr ""
msgstr "这阶段我的计划收入"
#. module: sale_crm
#: code:addons/sale_crm/wizard/crm_make_sale.py:110
#, python-format
msgid "Opportunity '%s' is converted to Quotation."
msgstr ""
msgstr "商机 '%s' 被转换为报价单"
#. module: sale_crm
#: view:sale.order:0
+6 -6
View File
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 00:37+0000\n"
"PO-Revision-Date: 2011-06-28 05:04+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"PO-Revision-Date: 2012-08-31 14:59+0000\n"
"Last-Translator: ccdos <Unknown>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\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-28 06:34+0000\n"
"X-Generator: Launchpad (build 15864)\n"
"X-Launchpad-Export-Date: 2012-09-01 04:55+0000\n"
"X-Generator: Launchpad (build 15890)\n"
#. module: sale_mrp
#: help:mrp.production,sale_ref:0
@@ -40,12 +40,12 @@ msgstr "销售名称"
#. module: sale_mrp
#: sql_constraint:mrp.production:0
msgid "Reference must be unique per Company!"
msgstr ""
msgstr "编号必须在公司内唯一!"
#. module: sale_mrp
#: constraint:mrp.production:0
msgid "Order quantity cannot be negative or zero!"
msgstr ""
msgstr "订单数量不能是负数或者0!"
#. module: sale_mrp
#: help:mrp.production,sale_name:0
+2 -1
View File
@@ -23,6 +23,7 @@ import random
import time
from urllib import quote_plus
import uuid
from openerp import SUPERUSER_ID
import simplejson
@@ -545,7 +546,7 @@ class share_wizard(osv.TransientModel):
_logger.debug("Copying rule %s (%s) on model %s with domain: %s", rule.name, rule.id, model.model, rule.domain_force)
else:
# otherwise we can simply link the rule to keep it dynamic
rule_obj.write(cr, 1, [rule.id], {
rule_obj.write(cr, SUPERUSER_ID, [rule.id], {
'groups': [(4,group_id)]
})
_logger.debug("Linking rule %s (%s) on model %s with domain: %s", rule.name, rule.id, model.model, rule.domain_force)
+5 -5
View File
@@ -1379,13 +1379,13 @@ class stock_picking(osv.osv):
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s has been <b>created</b>.") % (self._get_document_type(obj.type)), context=context)
self.message_post(cr, uid, [obj.id], body=_("%s has been <b>created</b>.") % (self._get_document_type(obj.type)), subtype="new", context=context)
def scrap_send_note(self, cr, uid, ids, quantity, uom, name, context=None):
return self.message_post(cr, uid, ids, body= _("%s %s %s has been <b>moved to</b> scrap.") % (quantity, uom, name), context=context)
return self.message_post(cr, uid, ids, body= _("%s %s %s has been <b>moved to</b> scrap.") % (quantity, uom, name), subtype="moved", context=context)
def back_order_send_note(self, cr, uid, ids, back_name, context=None):
return self.message_post(cr, uid, ids, body=_("Back order <em>%s</em> has been <b>created</b>.") % (back_name), context=context)
return self.message_post(cr, uid, ids, body=_("Back order <em>%s</em> has been <b>created</b>.") % (back_name), subtype="new", context=context)
def ship_done_send_note(self, cr, uid, ids, context=None):
type_dict = {
@@ -1394,11 +1394,11 @@ class stock_picking(osv.osv):
'internal': 'moved',
}
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Products have been <b>%s</b>.") % (type_dict.get(obj.type, 'move done')), context=context)
self.message_post(cr, uid, [obj.id], body=_("Products have been <b>%s</b>.") % (type_dict.get(obj.type, 'move done')), subtype=type_dict.get(obj.type), context=context)
def ship_cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s has been <b>cancelled</b>.") % (self._get_document_type(obj.type)), context=context)
self.message_post(cr, uid, [obj.id], body=_("%s has been <b>cancelled</b>.") % (self._get_document_type(obj.type)), subtype="cancelled", context=context)
stock_picking()
+29
View File
@@ -166,5 +166,34 @@ watch your stock valuation, and track production lots upstream and downstream (b
<field name="lot_stock_id" ref="stock_location_stock"/>
<field name="lot_output_id" ref="stock_location_output"/>
</record>
<!--
Mail: mail.message.subtype
-->
<record id="mail.mail_subtype_new" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('stock.model_stock_picking_in')),(4,ref('stock.model_stock_picking_out'))]"/>
</record>
<record id="mail.mail_subtype_delivered" model="mail.message.subtype">
<field name="name">delivered</field>
<field name="model_ids" eval="[(4,ref('stock.model_stock_picking_out'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_received" model="mail.message.subtype">
<field name="name">received</field>
<field name="model_ids" eval="[(4,ref('stock.model_stock_picking_in'))]"/>
<field name="default" eval="False"/>
</record>
<record id="mail.mail_subtype_cancelled" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('stock.model_stock_picking_in')),(4,ref('stock.model_stock_picking_out'))]"/>
</record>
<record id="mail.mail_subtype_email" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('stock.model_stock_picking_in')),(4,ref('stock.model_stock_picking_out'))]"/>
</record>
<record id="mail.mail_subtype_comment" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('stock.model_stock_picking_in')),(4,ref('stock.model_stock_picking_out'))]"/>
</record>
<record id="mail.mail_subtype_other" model="mail.message.subtype">
<field name="model_ids" eval="[(4,ref('stock.model_stock_picking_in')),(4,ref('stock.model_stock_picking_out'))]"/>
</record>
</data>
</openerp>