[MERGE] forward port branch 10.0 up to 0acd63d
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
<field name="report_name">Invoice_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field>
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<p>Dear ${object.partner_id.name}
|
||||
<p>Dear ${object.partner_id.name}
|
||||
% set access_action = object.get_access_action()
|
||||
% set access_url = access_action['type'] == 'ir.actions.act_url' and access_action['url'] or '/report/pdf/account.report_invoice/' + str(object.id)
|
||||
% set is_online = access_action and access_action['type'] == 'ir.actions.act_url'
|
||||
@@ -69,7 +69,7 @@ from ${object.company_id.name}.
|
||||
<!-- HEADER -->
|
||||
<tr>
|
||||
<td align="center" style="min-width: 590px;">
|
||||
<table width="590" border="0" cellpadding="0" bgcolor="#a24689" style="min-width: 590px; background-color: rgb(162, 70, 137); padding: 20px;">
|
||||
<table width="590" border="0" cellpadding="0" bgcolor="#875A7B" style="min-width: 590px; background-color: rgb(135,90,123); padding: 20px;">
|
||||
<tr>
|
||||
<td valign="middle">
|
||||
<span style="font-size:20px; color:white; font-weight: bold;">
|
||||
@@ -100,7 +100,7 @@ from ${object.company_id.name}.
|
||||
<!-- FOOTER -->
|
||||
<tr>
|
||||
<td align="center" style="min-width: 590px;">
|
||||
<table width="590" border="0" cellpadding="0" bgcolor="#a24689" style="min-width: 590px; background-color: rgb(162, 70, 137); padding: 20px;">
|
||||
<table width="590" border="0" cellpadding="0" bgcolor="#875A7B" style="min-width: 590px; background-color: rgb(135,90,123); padding: 20px;">
|
||||
<tr>
|
||||
<td valign="middle" align="left" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;">
|
||||
${user.company_id.name}<br/>
|
||||
|
||||
@@ -136,6 +136,13 @@ class AccountAccount(models.Model):
|
||||
for account in self:
|
||||
if (account.company_id.id <> vals['company_id']) and move_lines:
|
||||
raise UserError(_('You cannot change the owner company of an account that already contains journal items.'))
|
||||
# If user change the reconcile flag, all aml should be recomputed for that account and this is very costly.
|
||||
# So to prevent some bugs we add a constraint saying that you cannot change the reconcile field if there is any aml existing
|
||||
# for that account.
|
||||
if vals.get('reconcile'):
|
||||
move_lines = self.env['account.move.line'].search([('account_id', 'in', self.ids)], limit=1)
|
||||
if len(move_lines):
|
||||
raise UserError(_('You cannot change the value of the reconciliation on this account as it already has some moves'))
|
||||
return super(AccountAccount, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -1425,7 +1425,7 @@ class AccountPaymentTerm(models.Model):
|
||||
class AccountPaymentTermLine(models.Model):
|
||||
_name = "account.payment.term.line"
|
||||
_description = "Payment Term Line"
|
||||
_order = "sequence"
|
||||
_order = "sequence, id"
|
||||
|
||||
value = fields.Selection([
|
||||
('balance', 'Balance'),
|
||||
@@ -1433,7 +1433,7 @@ class AccountPaymentTermLine(models.Model):
|
||||
('fixed', 'Fixed Amount')
|
||||
], string='Type', required=True, default='balance',
|
||||
help="Select here the kind of valuation related to this payment term line.")
|
||||
value_amount = fields.Float(string='Value', digits=dp.get_precision('Payment Term'), help="For percent enter a ratio between 0-100.")
|
||||
value_amount = fields.Float(string='Value', digits=dp.get_precision('Payment Terms'), help="For percent enter a ratio between 0-100.")
|
||||
days = fields.Integer(string='Number of Days', required=True, default=0)
|
||||
option = fields.Selection([
|
||||
('day_after_invoice_date', 'Day(s) after the invoice date'),
|
||||
|
||||
@@ -885,6 +885,8 @@ class AccountMoveLine(models.Model):
|
||||
vals['date'] = self._context.get('date_p') or time.strftime('%Y-%m-%d')
|
||||
if 'name' not in vals:
|
||||
vals['name'] = self._context.get('comment') or _('Write-Off')
|
||||
if 'analytic_account_id' not in vals:
|
||||
vals['analytic_account_id'] = self.env.context.get('analytic_id', False)
|
||||
#compute the writeoff amount if not given
|
||||
if 'credit' not in vals and 'debit' not in vals:
|
||||
amount = sum([r.amount_residual for r in self])
|
||||
@@ -901,8 +903,8 @@ class AccountMoveLine(models.Model):
|
||||
# Writeoff line in the account of self
|
||||
first_line_dict = vals.copy()
|
||||
first_line_dict['account_id'] = self[0].account_id.id
|
||||
if 'analytic_account_id' in vals:
|
||||
del vals['analytic_account_id']
|
||||
if 'analytic_account_id' in first_line_dict:
|
||||
del first_line_dict['analytic_account_id']
|
||||
|
||||
# Writeoff line in specified writeoff account
|
||||
second_line_dict = vals.copy()
|
||||
|
||||
@@ -92,11 +92,12 @@ class ReportAgedPartnerBalance(models.AbstractModel):
|
||||
for partial_line in line.matched_credit_ids:
|
||||
if partial_line.create_date[:10] <= date_from:
|
||||
line_amount -= partial_line.amount
|
||||
undue_amounts[partner_id] += line_amount
|
||||
lines[partner_id].append({
|
||||
'line': line,
|
||||
'amount': line_amount,
|
||||
'period': 6,
|
||||
if not self.env.user.company_id.currency_id.is_zero(line_amount):
|
||||
undue_amounts[partner_id] += line_amount
|
||||
lines[partner_id].append({
|
||||
'line': line,
|
||||
'amount': line_amount,
|
||||
'period': 6,
|
||||
})
|
||||
|
||||
# Use one query per period and store results in history (a list variable)
|
||||
@@ -144,12 +145,13 @@ class ReportAgedPartnerBalance(models.AbstractModel):
|
||||
if partial_line.create_date[:10] <= date_from:
|
||||
line_amount -= partial_line.amount
|
||||
|
||||
partners_amount[partner_id] += line_amount
|
||||
lines[partner_id].append({
|
||||
'line': line,
|
||||
'amount': line_amount,
|
||||
'period': i + 1,
|
||||
})
|
||||
if not self.env.user.company_id.currency_id.is_zero(line_amount):
|
||||
partners_amount[partner_id] += line_amount
|
||||
lines[partner_id].append({
|
||||
'line': line,
|
||||
'amount': line_amount,
|
||||
'period': i + 1,
|
||||
})
|
||||
history.append(partners_amount)
|
||||
|
||||
for partner in partners:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Turn Quotes Into Invoices with minimal effort</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Turn Quotes Into Invoices with minimal effort</h2>
|
||||
<h3 class="oe_slogan oe_mb32">Bill automatically based on SO, delivery orders, contracts or time and material</h3>
|
||||
<div class="oe_span4 text-center">
|
||||
<span class="fa fa-copy fa-2x"/>
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Get paid faster</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Get paid faster</h2>
|
||||
<h3 class="oe_slogan">Online payments and automated follow-ups</h3>
|
||||
<p class="oe_mt32">
|
||||
Accept online payments via Paypal, Ogone, Buckaroo or Adyen. Eliminate the stress of sending reminders for late or outstanding payments. Simply set up and automate follow-ups to get paid quickly.
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Simplify your accounting</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Simplify your accounting</h2>
|
||||
<h3 class="oe_slogan">Keep track of bank account movements and the status of invoices</h3>
|
||||
<p class="oe_mt32">
|
||||
Use a status overview to help keep track of drafts, paid and unpaid invoices. Automatically record all transactions from your bank accounts by importing and reconciling your bank statements.
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Analyze your sales</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Analyze your sales</h2>
|
||||
<h3 class="oe_slogan">Access insightful data to get the big picture</h3>
|
||||
<div class="oe_span6">
|
||||
<p class='oe_mt32 text-justify'>
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
<menuitem id="account_management_menu" name="Management" parent="account.menu_finance_configuration" sequence="3" groups="group_account_manager"/>
|
||||
<menuitem id="menu_analytic_accounting" name="Analytic Accounting" parent="menu_finance_configuration" groups="analytic.group_analytic_accounting" sequence="5"/>
|
||||
<menuitem id="menu_config_multi_currency" name="Multi-Currencies" parent="menu_finance_configuration" sequence="9" groups="base.group_multi_currency"/>
|
||||
<menuitem id="menu_action_currency_form" action="base.action_currency_form" name="Currencies" parent="menu_config_multi_currency" sequence="20" groups="base.group_no_one"/>
|
||||
|
||||
<menuitem id="menu_action_currency_form" action="base.action_currency_form" name="Currencies" parent="menu_config_multi_currency" sequence="20" groups="base.group_multi_currency"/>
|
||||
<menuitem id="menu_account_reports" name="Financial Reports" parent="menu_finance_configuration" groups="group_account_user"/>
|
||||
|
||||
|
||||
|
||||
@@ -98,10 +98,7 @@
|
||||
<group>
|
||||
<field name="payment_type" widget="radio" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="partner_type" nolabel="1" widget="selection" string="" attrs="{'required': [('payment_type', 'in', ('inbound', 'outbound'))], 'invisible': [('payment_type', 'not in', ('inbound', 'outbound'))], 'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<div>
|
||||
<field name="partner_id" nolabel="1" attrs="{'required': [('partner_type', '=', 'customer')], 'invisible': [('partner_type', '!=', 'customer')], 'readonly': [('state', '!=', 'draft')]}" context="{'default_is_company': True, 'default_customer': True}" domain = "[('customer', '=', True)]"/>
|
||||
<field name="partner_id" nolabel="1" attrs="{'required': [('partner_type', '=', 'supplier')], 'invisible': [('partner_type', '!=', 'supplier')], 'readonly': [('state', '!=', 'draft')]}" context="{'default_is_company': True, 'default_supplier': True}" domain = "[('supplier', '=', True)]"/>
|
||||
</div>
|
||||
<field name="partner_id" nolabel="1" attrs="{'required': [('payment_type', 'in', ('inbound', 'outbound'))], 'invisible': [('payment_type', 'not in', ('inbound', 'outbound'))], 'readonly': [('state', '!=', 'draft')]}" context="{'default_is_company': True, 'default_supplier': payment_type == 'outbound', 'default_customer': payment_type == 'inbound'}"/>
|
||||
<field name="journal_id" widget="selection" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="destination_journal_id" widget="selection" attrs="{'required': [('payment_type', '=', 'transfer')], 'invisible': [('payment_type', '!=', 'transfer')], 'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="hide_payment_method" invisible="1"/>
|
||||
|
||||
@@ -39,12 +39,12 @@
|
||||
<field name="tax_ids" widget="one2many_list" nolabel="1">
|
||||
<tree name="tax_map_tree" string="Tax Mapping" editable="bottom">
|
||||
<field name="tax_src_id" domain="[('type_tax_use', '!=', None)]"/>
|
||||
<field name="tax_dest_id" domain="[('type_tax_use', '!=', None), ('price_include', '=', False)]"/>
|
||||
<field name="tax_dest_id" domain="[('type_tax_use', '!=', None)]"/>
|
||||
</tree>
|
||||
<form name="tax_map_form" string="Tax Mapping">
|
||||
<group>
|
||||
<field name="tax_src_id" domain="[('type_tax_use', '!=', None)]"/>
|
||||
<field name="tax_dest_id" domain="[('type_tax_use', '!=', None), ('price_include', '=', False)]"/>
|
||||
<field name="tax_dest_id" domain="[('type_tax_use', '!=', None)]"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -44,14 +44,24 @@
|
||||
<t t-call="web_planner.page">
|
||||
<t t-set="page_title">Your Company</t>
|
||||
<p class="text-muted">
|
||||
The first step is to set up your company information. This is mostly used for invoicing.
|
||||
The first step is to set up your company's information. This is mostly used in printed business documents like orders and invoices.
|
||||
</p>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Logo</th>
|
||||
<td>
|
||||
<img width="150" height="90" alt="set company logo" t-attf-src="data:image/gif;base64, #{company_id.logo}"/>
|
||||
</td><td>
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}" target="_blank">
|
||||
Change
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Legal Name</th>
|
||||
<td t-att-class="(company_id.name == 'YourCompany') and 'danger'"><div t-esc="company_id.name"/></td>
|
||||
<td>
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}">
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}" target="_blank">
|
||||
Change
|
||||
</a>
|
||||
</td>
|
||||
@@ -62,17 +72,17 @@
|
||||
t-options='{"widget": "contact", "fields": ["address"], "no_marker": True}'/>
|
||||
</td>
|
||||
<td>
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}">
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}" target="_blank">
|
||||
Change
|
||||
</a>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<th>Tax ID (VAT)</th>
|
||||
<th>Tax ID</th>
|
||||
<td t-att-class="(not company_id.vat) and 'danger'">
|
||||
<span t-field="company_id.vat"/>
|
||||
</td>
|
||||
<td>
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}">
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}" target="_blank">
|
||||
Change
|
||||
</a>
|
||||
</td>
|
||||
@@ -80,7 +90,7 @@
|
||||
<th>Main Currency</th>
|
||||
<td><span t-field="company_id.currency_id.display_name"/></td>
|
||||
<td>
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}">
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}" target="_blank">
|
||||
Change
|
||||
</a>
|
||||
</td>
|
||||
@@ -88,7 +98,7 @@
|
||||
<th>Terms & Conditions</th>
|
||||
<td></td>
|
||||
<td>
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}">
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('sales_team.action_sale_config', 'form')}&id=#{company_id.id}" target="_blank">
|
||||
Change
|
||||
</a>
|
||||
</td>
|
||||
@@ -101,15 +111,6 @@
|
||||
</td><td>
|
||||
<a id="review_payment_terms" class="fa fa-arrow-right" t-att-href="prepare_backend_url('account.action_payment_term_form')">Review Terms</a>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<th>Logo</th>
|
||||
<td>
|
||||
<img width="150" height="90" alt="set company logo" t-attf-src="data:image/gif;base64, #{company_id.logo}"/>
|
||||
</td><td>
|
||||
<a class="fa fa-arrow-right" t-attf-href="#{prepare_backend_url('base.action_res_company_form', 'form')}&id=#{company_id.id}">
|
||||
Change
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<menuitem
|
||||
id="menu_partner_ledger"
|
||||
name="Partner Ledger"
|
||||
parent="account.account_reports_management_menu"
|
||||
parent="account.menu_finance_legal_statement"
|
||||
action="action_account_partner_ledger_menu"
|
||||
groups="account.group_account_manager,account.group_account_user"
|
||||
/>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<menuitem
|
||||
id="menu_print_journal"
|
||||
name="Sale/Purchase Journal"
|
||||
parent="account.account_reports_management_menu"
|
||||
parent="account.menu_finance_legal_statement"
|
||||
action="action_account_print_journal_menu"
|
||||
groups="account.group_account_manager,account.group_account_user"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Beautiful accounting software</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Beautiful accounting software</h2>
|
||||
<h3 class="oe_slogan">Do more, in less time.</h3>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Popular features that will change your life</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Popular features that will change your life</h2>
|
||||
<h3 class="oe_slogan">All the time-saving tools you need to grow your business.</h3>
|
||||
</div>
|
||||
<div class="oe_row">
|
||||
@@ -30,7 +30,7 @@
|
||||
Create sharp and professional invoices, manage recurring billings, and easily track payments.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span4" style="color:#A24689;">
|
||||
<div class="oe_span4" style="color:#875A7B;">
|
||||
<img class="oe_picture" src="accounting_sc_01.png">
|
||||
</div>
|
||||
<div class="oe_span4 oe_mt32 text-right">
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Get paid faster</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Get paid faster</h2>
|
||||
<h3 class="oe_slogan">Electronic invoicing and automated follow-ups</h3>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32">
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Beautiful dynamic statements</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Beautiful dynamic statements</h2>
|
||||
<h3 class="oe_slogan">Your reports, the way you like them.</h3>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="accounting_sc_03.png">
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
<button name="validate" states="draft" string="Confirm" type="object" class="oe_highlight"/>
|
||||
<button type="object" name="compute_depreciation_board" string="Compute Depreciation" states="draft"/>
|
||||
<button name="set_to_close" states="open" string="Sell or Dispose" type="object" class="oe_highlight"/>
|
||||
<button name="set_to_draft" states="open" string="Set to Draft" type="object" />
|
||||
<button name="set_to_draft" string="Set to Draft" type="object" attrs="{'invisible': ['|', ('entry_count', '!=', 0), ('state', '!=', 'open')]}"/>
|
||||
<button name="%(action_asset_modify)d" states="open" string="Modify Depreciation" type="action"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,open"/>
|
||||
</header>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<p>A password reset was requested for the Odoo account linked to this email.</p>
|
||||
<p>You may change your password by following this link which will remain valid during 24 hours:</p>
|
||||
<div style="text-align: center; margin-top: 16px;">
|
||||
<a href="${object.signup_url}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">Change password</a>
|
||||
<a href="${object.signup_url}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Change password</a>
|
||||
</div>
|
||||
<p>If you do not expect this, you can safely ignore this email.</p>
|
||||
<p>Best regards,</p>
|
||||
@@ -46,7 +46,7 @@
|
||||
<div style="padding:0px;width:600px;margin:auto; margin-top: 10px; background: #fff repeat top /100%;color:#777777">
|
||||
${user.signature | safe}
|
||||
<p style="font-size: 11px; margin-top: 10px;">
|
||||
<strong>Sent by ${user.company_id.name} using <a href="www.odoo.com" style="text-decoration:none; color: #a24689;">Odoo</a></strong>
|
||||
<strong>Sent by ${user.company_id.name} using <a href="www.odoo.com" style="text-decoration:none; color: #875A7B;">Odoo</a></strong>
|
||||
</p>
|
||||
</div>]]></field>
|
||||
<field name="user_signature" eval="False"/>
|
||||
@@ -78,14 +78,14 @@
|
||||
To accept the invitation, click on the following link:
|
||||
</p>
|
||||
<div style="text-align: center; margin-top: 16px;">
|
||||
<a href="${object.signup_url}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">Accept invitation to "${object.company_id.name}"</a>
|
||||
<a href="${object.signup_url}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Accept invitation to "${object.company_id.name}"</a>
|
||||
</div>
|
||||
<p>Best regards,</p>
|
||||
</div>
|
||||
<div style="padding:0px;width:600px;margin:auto; margin-top: 10px; background: #fff repeat top /100%;color:#777777">
|
||||
${user.signature | safe}
|
||||
<p style="font-size: 11px; margin-top: 10px;">
|
||||
<strong>Sent by ${user.company_id.name} using <a href="www.odoo.com" style="text-decoration:none; color: #a24689;">Odoo</a></strong>
|
||||
<strong>Sent by ${user.company_id.name} using <a href="www.odoo.com" style="text-decoration:none; color: #875A7B;">Odoo</a></strong>
|
||||
</p>
|
||||
</div>]]></field>
|
||||
<field name="user_signature" eval="False"/>
|
||||
|
||||
@@ -124,7 +124,7 @@ class BaseActionRule(models.Model):
|
||||
|
||||
def _update_registry(self):
|
||||
""" Update the registry after a modification on action rules. """
|
||||
if self.env.registry.ready:
|
||||
if self.env.registry.ready and not self.env.context.get('import_file'):
|
||||
# for the sake of simplicity, simply force the registry to reload
|
||||
self._cr.commit()
|
||||
self.env.reset()
|
||||
|
||||
@@ -649,7 +649,7 @@ class Import(models.TransientModel):
|
||||
}]
|
||||
|
||||
_logger.info('importing %d rows...', len(data))
|
||||
import_result = self.env[self.res_model].load(import_fields, data)
|
||||
import_result = self.env[self.res_model].with_context(import_file=True).load(import_fields, data)
|
||||
_logger.info('done')
|
||||
|
||||
# If transaction aborted, RELEASE SAVEPOINT is going to raise
|
||||
|
||||
@@ -45,6 +45,10 @@ class IrModule(models.Model):
|
||||
|
||||
for kind in ['data', 'init_xml', 'update_xml']:
|
||||
for filename in terp[kind]:
|
||||
ext = os.path.splitext(filename)[1].lower()
|
||||
if ext not in ('.xml', '.csv', '.sql'):
|
||||
_logger.info("module %s: skip unsupported file %s", module, filename)
|
||||
continue
|
||||
_logger.info("module %s: loading %s", module, filename)
|
||||
noupdate = False
|
||||
if filename.endswith('.csv') and kind in ('init', 'init_xml'):
|
||||
|
||||
@@ -90,9 +90,9 @@
|
||||
</td>
|
||||
<td valign="center" align="right" width="340" style="padding:10px 10px 10px 5px; font-size: 12px;">
|
||||
<p>
|
||||
<a href="/calendar/meeting/accept?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">Accept</a>
|
||||
<a href="/calendar/meeting/decline?db=${'dbname' in ctx and ctx['dbname'] or '' }&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">Decline</a>
|
||||
<a href="/calendar/meeting/view?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">View</a>
|
||||
<a href="/calendar/meeting/accept?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Accept</a>
|
||||
<a href="/calendar/meeting/decline?db=${'dbname' in ctx and ctx['dbname'] or '' }&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Decline</a>
|
||||
<a href="/calendar/meeting/view?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">View</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr></tbody>
|
||||
@@ -117,14 +117,14 @@
|
||||
${object.event_id.user_id.partner_id.name} invited you for the ${object.event_id.name} meeting of ${object.event_id.user_id.company_id.name}.</p>
|
||||
<table style="margin-top: 20px;"><tr>
|
||||
<td>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;background:#a24689;padding-top: 4px;">
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;background:#875A7B;padding-top: 4px;">
|
||||
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
</div>
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #F8F8F8;width: 130px;border:1px solid #a24689;">
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #F8F8F8;width: 130px;border:1px solid #875A7B;">
|
||||
${object.event_id.get_interval('day', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#a24689'>${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}</div>
|
||||
<div style="border-collapse:separate;color: #5F5F5F;text-align:center;width: 130px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid #a24689;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#875A7B'>${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}</div>
|
||||
<div style="border-collapse:separate;color: #5F5F5F;text-align:center;width: 130px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid #875A7B;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}</div>
|
||||
</td>
|
||||
<td width="20px;"/>
|
||||
<td>
|
||||
@@ -181,9 +181,9 @@
|
||||
</td>
|
||||
<td valign="center" align="right" width="340" style="padding:10px 10px 10px 5px; font-size: 12px;">
|
||||
<p>
|
||||
<a href="/calendar/meeting/accept?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">Accept</a>
|
||||
<a href="/calendar/meeting/decline?db=${'dbname' in ctx and ctx['dbname'] or '' }&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">Decline</a>
|
||||
<a href="/calendar/meeting/view?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">View</a>
|
||||
<a href="/calendar/meeting/accept?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Accept</a>
|
||||
<a href="/calendar/meeting/decline?db=${'dbname' in ctx and ctx['dbname'] or '' }&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Decline</a>
|
||||
<a href="/calendar/meeting/view?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">View</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr></tbody>
|
||||
@@ -209,14 +209,14 @@
|
||||
</p>
|
||||
<table style="margin-top: 20px;"><tr>
|
||||
<td>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;background:#a24689;padding-top: 4px;">
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;background:#875A7B;padding-top: 4px;">
|
||||
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
</div>
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #F8F8F8;width: 130px;border:1px solid #a24689;">
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #F8F8F8;width: 130px;border:1px solid #875A7B;">
|
||||
${object.event_id.get_interval('day', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#a24689'>${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}</div>
|
||||
<div style="border-collapse:separate;color: #5F5F5F;text-align:center;width: 130px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid #a24689;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#875A7B'>${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}</div>
|
||||
<div style="border-collapse:separate;color: #5F5F5F;text-align:center;width: 130px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid #875A7B;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}</div>
|
||||
</td>
|
||||
<td width="20px;"/>
|
||||
<td>
|
||||
@@ -273,9 +273,9 @@
|
||||
</td>
|
||||
<td valign="center" align="right" width="340" style="padding:10px 10px 10px 5px; font-size: 12px;">
|
||||
<p>
|
||||
<a href="/calendar/meeting/accept?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">Accept</a>
|
||||
<a href="/calendar/meeting/decline?db=${'dbname' in ctx and ctx['dbname'] or '' }&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">Decline</a>
|
||||
<a href="/calendar/meeting/view?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px">View</a>
|
||||
<a href="/calendar/meeting/accept?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Accept</a>
|
||||
<a href="/calendar/meeting/decline?db=${'dbname' in ctx and ctx['dbname'] or '' }&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Decline</a>
|
||||
<a href="/calendar/meeting/view?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">View</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr></tbody>
|
||||
@@ -301,14 +301,14 @@
|
||||
</p>
|
||||
<table style="margin-top: 20px;"><tr>
|
||||
<td>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;background:#a24689;padding-top: 4px;">
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;background:#875A7B;padding-top: 4px;">
|
||||
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
</div>
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #F8F8F8;width: 130px;border:1px solid #a24689;">
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #F8F8F8;width: 130px;border:1px solid #875A7B;">
|
||||
${object.event_id.get_interval('day', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#a24689'>${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}</div>
|
||||
<div style="border-collapse:separate;color: #5F5F5F;text-align:center;width: 130px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid #a24689;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#875A7B'>${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}</div>
|
||||
<div style="border-collapse:separate;color: #5F5F5F;text-align:center;width: 130px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid #875A7B;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}</div>
|
||||
</td>
|
||||
<td width="20px;"/>
|
||||
<td>
|
||||
|
||||
@@ -376,7 +376,7 @@ class AlarmManager(models.AbstractModel):
|
||||
if meeting.recurrency:
|
||||
b_found = False
|
||||
last_found = False
|
||||
for one_date in meeting.get_recurrent_date_by_event():
|
||||
for one_date in meeting._get_recurrent_date_by_event():
|
||||
in_date_format = one_date.replace(tzinfo=None)
|
||||
last_found = self.do_check_alarm_for_one_date(in_date_format, meeting, max_delta, time_limit, 'notification', after=partner.calendar_last_notif_ack)
|
||||
if last_found:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import controllers
|
||||
import models
|
||||
import report
|
||||
import wizard
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*
|
||||
import main
|
||||
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.controllers.main import MailController
|
||||
from odoo import http
|
||||
|
||||
|
||||
class CrmController(http.Controller):
|
||||
|
||||
@http.route('/lead/case_mark_won', type='http', auth='user', methods=['GET'])
|
||||
def crm_lead_case_mark_won(self, res_id, token):
|
||||
comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token)
|
||||
if comparison and record:
|
||||
try:
|
||||
record.case_mark_won()
|
||||
except Exception:
|
||||
return MailController._redirect_to_messaging()
|
||||
return redirect
|
||||
|
||||
@http.route('/lead/case_mark_lost', type='http', auth='user', methods=['GET'])
|
||||
def crm_lead_case_mark_lost(self, res_id, token):
|
||||
comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token)
|
||||
if comparison and record:
|
||||
try:
|
||||
record.case_mark_lost()
|
||||
except Exception:
|
||||
return MailController._redirect_to_messaging()
|
||||
return redirect
|
||||
|
||||
@http.route('/lead/convert', type='http', auth='user', methods=['GET'])
|
||||
def crm_lead_convert(self, res_id, token):
|
||||
comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token)
|
||||
if comparison and record:
|
||||
try:
|
||||
record.convert_opportunity(record.partner_id.id)
|
||||
except Exception:
|
||||
return MailController._redirect_to_messaging()
|
||||
return redirect
|
||||
@@ -22,7 +22,7 @@
|
||||
<img src="web/static/src/img/logo.png"/>
|
||||
</td>
|
||||
<td style="vertical-align: top; padding: 8px 10px;text-align: left;font-size: 14px;">
|
||||
<a href="web/login" style="float:right ;margin:15px auto;background: #a24689;border-radius: 5px;color: #ffffff;font-size: 16px;padding: 10px 20px 10px 20px;text-decoration: none;">Auto Login</a>
|
||||
<a href="web/login" style="float:right ;margin:15px auto;background: #875A7B;border-radius: 5px;color: #ffffff;font-size: 16px;padding: 10px 20px 10px 20px;text-decoration: none;">Auto Login</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -31,7 +31,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:15px 10px;font-size:20px">
|
||||
<p style="color:#a24689;margin:0" >Hooray!</p><br>
|
||||
<p style="color:#875A7B;margin:0" >Hooray!</p><br>
|
||||
<p dir="ltr" style="font-size:15px;margin-top:0pt;margin-bottom:0pt;">
|
||||
<span>Your Odoo CRM application is up and running.</span></p><br>
|
||||
<p dir="ltr" style="margin-top:0pt;margin-bottom:8pt;">
|
||||
@@ -56,7 +56,7 @@
|
||||
<span><a href="/web#view_type=list&model=crm.lead&action=crm.crm_lead_all_leads">
|
||||
<span style="font-weight:bold; text-decoration:underline;">CRM planner</span></a></span>
|
||||
<span> to activate extra features</span>
|
||||
<span style="color:#a24689;margin:0;font-weight:bold">(${user.env['web.planner'].get_planner_progress('planner_crm')}% done)</span>
|
||||
<span style="color:#875A7B;margin:0;font-weight:bold">(${user.env['web.planner'].get_planner_progress('planner_crm')}% done)</span>
|
||||
</p>
|
||||
<ul style="margin-top:0pt;margin-bottom:0pt;font-size:13px;list-style-type:disc;">
|
||||
<li dir="ltr">
|
||||
|
||||
@@ -1038,11 +1038,11 @@ class Lead(FormatAddress, models.Model):
|
||||
|
||||
self.ensure_one()
|
||||
if self.type == 'lead':
|
||||
convert_action = self._notification_link_helper('method', method='convert_opportunity', partner_id=self.partner_id.id)
|
||||
convert_action = self._notification_link_helper('controller', controller='/lead/convert')
|
||||
salesman_actions = [{'url': convert_action, 'title': _('Convert to opportunity')}]
|
||||
else:
|
||||
won_action = self._notification_link_helper('method', method='action_set_won')
|
||||
lost_action = self._notification_link_helper('method', method='action_set_lost')
|
||||
won_action = self._notification_link_helper('controller', controller='/lead/case_mark_won')
|
||||
lost_action = self._notification_link_helper('controller', controller='/lead/case_mark_lost')
|
||||
salesman_actions = [
|
||||
{'url': won_action, 'title': _('Won')},
|
||||
{'url': lost_action, 'title': _('Lost')}]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">The real customer centric CRM</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">The real customer centric CRM</h2>
|
||||
<h3 class="oe_slogan">Track leads, close opportunities and get accurate forecasts.</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="crm_sc_00.png">
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Get accurate forecasts</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Get accurate forecasts</h2>
|
||||
<h3 class="oe_slogan">Use actionable data to make better decisions</h3>
|
||||
<div class="oe_span6 text-justify oe_mt32">
|
||||
<span class="fa fa-search fa-2x pull-left"/>
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Get more done in less time</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Get more done in less time</h2>
|
||||
<h3 class="oe_slogan">Great Tool = Happy People</h3>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="crm_sc_02a.png">
|
||||
@@ -51,7 +51,7 @@
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div class="oe_span6">
|
||||
<h2 class="oe_slogan" style="color:#A24689; text-align: left;">Real-time overview</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B; text-align: left;">Real-time overview</h2>
|
||||
<h3 class="oe_slogan" style="text-align: left;">of your performances & next activities</h3>
|
||||
<p class="oe_mt32">
|
||||
Know exactly how you perform compared to your monthly targets.<br/>Organize your work based on your scheduled next actions.
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Stay connected to your customers</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Stay connected to your customers</h2>
|
||||
<h3 class="oe_slogan">Real-time messaging to enhance collaboration</h3>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="crm_sc_04.png"/>
|
||||
|
||||
@@ -526,7 +526,7 @@
|
||||
<field name="source_id" invisible="1"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="planned_revenue" sum="Expected Revenues"/>
|
||||
<field name="probability" avg="Avg. of Probability"/>
|
||||
<field name="probability" />
|
||||
<field name="team_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="referred" invisible="1"/>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<h2 style="font-weight:300;">
|
||||
Hi ${object.name}, here is your order confirmation for
|
||||
% if 'website_url' in object.event_id and object.event_id.website_url:
|
||||
<a href="${object.event_id.website_url}" style="color:#a24689;text-decoration:none;">${object.event_id.name}</a>
|
||||
<a href="${object.event_id.website_url}" style="color:#875A7B;text-decoration:none;">${object.event_id.name}</a>
|
||||
% else:
|
||||
<strong>${object.event_id.name}</strong>
|
||||
% endif
|
||||
@@ -110,7 +110,7 @@
|
||||
<tr>
|
||||
<td style="padding:10px 0px 25px 0px;">
|
||||
<h2 style="font-weight:300;margin:10px 0px">Questions about this event?</h2>
|
||||
<div>Please contact the organizer at <a href="mailto:${object.event_id.organizer_id.email}" style="text-decoration:none;color:#a24689;">${object.event_id.organizer_id.email}</a></div>
|
||||
<div>Please contact the organizer at <a href="mailto:${object.event_id.organizer_id.email}" style="text-decoration:none;color:#875A7B;">${object.event_id.organizer_id.email}</a></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -119,9 +119,9 @@
|
||||
<tr>
|
||||
<td style="padding:25px 0px;">
|
||||
<strong>Add this event to your calendar</strong>
|
||||
<a href="https://www.google.com/calendar/render?action=TEMPLATE&text=${object.event_id.name}&dates=${date_begin}/${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #a24689;color:#a24689;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(162,70,137)/16" style="vertical-align:middle;" height="16"> Google</a>
|
||||
<a href="https://bay02.calendar.live.com/calendar/calendar.aspx?rru=addevent&summary=${object.event_id.name}&dtstart=${date_begin}&dtend=${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #a24689;color:#a24689;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(162,70,137)/16" style="vertical-align:middle;" height="16"> Outlook</a>
|
||||
<a href="https://calendar.yahoo.com/?v=60&view=d&type=20&title=${object.event_id.name}&in_loc=${location}&st=${format_tz(object.event_id.date_begin, tz='UTC', format='%Y%m%dT%H%M%S')}&et=${format_tz(object.event_id.date_end, tz='UTC', format='%Y%m%dT%H%M%S')}" style="padding:3px 5px;border:1px solid #a24689;color:#a24689;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(162,70,137)/16" style="vertical-align:middle;" height="16"> Yahoo</a>
|
||||
<a href="https://www.google.com/calendar/render?action=TEMPLATE&text=${object.event_id.name}&dates=${date_begin}/${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Google</a>
|
||||
<a href="https://bay02.calendar.live.com/calendar/calendar.aspx?rru=addevent&summary=${object.event_id.name}&dtstart=${date_begin}&dtend=${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Outlook</a>
|
||||
<a href="https://calendar.yahoo.com/?v=60&view=d&type=20&title=${object.event_id.name}&in_loc=${location}&st=${format_tz(object.event_id.date_begin, tz='UTC', format='%Y%m%dT%H%M%S')}&et=${format_tz(object.event_id.date_end, tz='UTC', format='%Y%m%dT%H%M%S')}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Yahoo</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -138,7 +138,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding-top:10px;font-size: 12px;">
|
||||
<div>This email was sent to <a href="mailto:${object.email}" style="text-decoration:none;color:#a24689;">${object.email}</a></div>
|
||||
<div>This email was sent to <a href="mailto:${object.email}" style="text-decoration:none;color:#875A7B;">${object.email}</a></div>
|
||||
<p style="margin:0px 0px 9px 0px;padding-top:10px;">Copyright © ${object.company_id.name}</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -186,7 +186,7 @@
|
||||
<p style="font-size:16px;">
|
||||
Please keep in mind that the event
|
||||
% if 'website_url' in object.event_id and object.event_id.website_url:
|
||||
<a href="${object.event_id.website_url}" style="color:#a24689;text-decoration:none;">${object.event_id.name}</a>
|
||||
<a href="${object.event_id.website_url}" style="color:#875A7B;text-decoration:none;">${object.event_id.name}</a>
|
||||
% else:
|
||||
<strong>${object.event_id.name}</strong>
|
||||
% endif
|
||||
@@ -243,7 +243,7 @@
|
||||
<tr>
|
||||
<td style="padding:10px 0px 25px 0px;">
|
||||
<h2 style="font-weight:300;margin:10px 0px">Questions about this event?</h2>
|
||||
<div>Please contact the organizer at <a href="mailto:${object.event_id.organizer_id.email}" style="text-decoration:none;color:#a24689;">${object.event_id.organizer_id.email}</a></div>
|
||||
<div>Please contact the organizer at <a href="mailto:${object.event_id.organizer_id.email}" style="text-decoration:none;color:#875A7B;">${object.event_id.organizer_id.email}</a></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -252,9 +252,9 @@
|
||||
<tr>
|
||||
<td style="padding:25px 0px;">
|
||||
<strong>Add this event to your calendar</strong>
|
||||
<a href="https://www.google.com/calendar/render?action=TEMPLATE&text=${object.event_id.name}&dates=${date_begin}/${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #a24689;color:#a24689;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(162,70,137)/16" style="vertical-align:middle;" height="16"> Google</a>
|
||||
<a href="https://bay02.calendar.live.com/calendar/calendar.aspx?rru=addevent&summary=${object.event_id.name}&dtstart=${date_begin}&dtend=${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #a24689;color:#a24689;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(162,70,137)/16" style="vertical-align:middle;" height="16"> Outlook</a>
|
||||
<a href="https://calendar.yahoo.com/?v=60&view=d&type=20&title=${object.event_id.name}&in_loc=${location}&st=${format_tz(object.event_id.date_begin, tz='UTC', format='%Y%m%dT%H%M%S')}&et=${format_tz(object.event_id.date_end, tz='UTC', format='%Y%m%dT%H%M%S')}" style="padding:3px 5px;border:1px solid #a24689;color:#a24689;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(162,70,137)/16" style="vertical-align:middle;" height="16"> Yahoo</a>
|
||||
<a href="https://www.google.com/calendar/render?action=TEMPLATE&text=${object.event_id.name}&dates=${date_begin}/${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Google</a>
|
||||
<a href="https://bay02.calendar.live.com/calendar/calendar.aspx?rru=addevent&summary=${object.event_id.name}&dtstart=${date_begin}&dtend=${date_end}&location=${location}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Outlook</a>
|
||||
<a href="https://calendar.yahoo.com/?v=60&view=d&type=20&title=${object.event_id.name}&in_loc=${location}&st=${format_tz(object.event_id.date_begin, tz='UTC', format='%Y%m%dT%H%M%S')}&et=${format_tz(object.event_id.date_end, tz='UTC', format='%Y%m%dT%H%M%S')}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"> Yahoo</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -271,7 +271,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding-top:10px;font-size: 12px;">
|
||||
<div>This email was sent to <a href="mailto:${object.email}" style="text-decoration:none;color:#a24689;">${object.email}</a></div>
|
||||
<div>This email was sent to <a href="mailto:${object.email}" style="text-decoration:none;color:#875A7B;">${object.email}</a></div>
|
||||
<p style="margin:0px 0px 9px 0px;padding-top:10px;">Copyright © ${object.company_id.name}</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -53,6 +53,9 @@ class RegistrationEditor(models.TransientModel):
|
||||
registration_line.registration_id.write(values)
|
||||
else:
|
||||
self.env['event.registration'].create(values)
|
||||
if self.env.context.get('active_model') == 'sale.order':
|
||||
for order in self.env['sale.order'].browse(self.env.context.get('active_ids', [])):
|
||||
order.order_line._update_registrations(confirm=True)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Odoo Fleet</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Odoo Fleet</h2>
|
||||
<h3 class="oe_slogan">Manage your vehicles, contracts, costs, insurances and assignments</h3>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Fleet management made easy</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Fleet management made easy</h2>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="vehicles_odometer.png">
|
||||
</div>
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Manage leasing and all other contracts</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Manage leasing and all other contracts</h2>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32 text-justify">
|
||||
Supervise all contracts for your vehicles and receive a warning email when contracts reach their expiration date. Several visual tools are put in place to ensure that you'll remember to renew (or end) your contract. Organize the services around the vehicles and communicate with the qualified service providers; manage invoices and notes. Set up a vehicle policy within your company, as well as an insurance policy in order to manage your fleet in the most efficient way.
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Monitor all costs at once</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Monitor all costs at once</h2>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="fleet_pie_chart.png">
|
||||
</div>
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Analysis and reporting</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Analysis and reporting</h2>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32 text-justify">
|
||||
Show all costs associated with a given vehicle or with a type of service. Compare different types of costs (which vehicles cost the most; which services have been performed on which vehicles etc.) by using the reporting tool. Get really helpful insights about the effective return of each vehicle in order to improve your fleet investments.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Modern open source HR solution</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Modern open source HR solution</h2>
|
||||
<h3 class="oe_slogan">Manage the most important asset in your company: <b>People</b></h3>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Successfully manage your employees</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Successfully manage your employees</h2>
|
||||
<h3 class="oe_slogan">Centralize all your HR information</h3>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="hr_departments.png">
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Boost Engagement With Social Tools</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Boost Engagement With Social Tools</h2>
|
||||
<h3 class="oe_slogan oe_mb32">Improve communication between employees and motivate them through rewards</h3>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="enterprise_social_network.png">
|
||||
|
||||
@@ -35,4 +35,5 @@ actions(Check in/Check out) performed by them.
|
||||
'qweb': [
|
||||
"static/src/xml/attendance.xml",
|
||||
],
|
||||
'application': True,
|
||||
}
|
||||
|
||||
@@ -21,13 +21,13 @@ class HrEmployee(models.Model):
|
||||
return barcode
|
||||
|
||||
barcode = fields.Char(string="Badge ID", help="ID used for employee identification.", default=_default_random_barcode, copy=False)
|
||||
pin = fields.Char(string="PIN", default=_default_random_pin, help="PIN used for Check In/Out in Attendance.", copy=False)
|
||||
pin = fields.Char(string="PIN", default=_default_random_pin, help="PIN used to Check In/Out in Kiosk Mode (if enabled in Configuration).", copy=False)
|
||||
|
||||
attendance_ids = fields.One2many('hr.attendance', 'employee_id', help='list of attendances for the employee')
|
||||
last_attendance_id = fields.Many2one('hr.attendance', compute='_compute_last_attendance_id')
|
||||
attendance_state = fields.Selection(string="Attendance", compute='_compute_attendance_state', selection=[('checked_out', "Checked out"), ('checked_in', "Checked in")])
|
||||
manual_attendance = fields.Boolean(string='Manual Attendance', compute='_compute_manual_attendance', inverse='_inverse_manual_attendance',
|
||||
help='The employee will have access to the "My Attendances" menu to perform his check in and out from his session')
|
||||
help='The employee will have access to the "My Attendances" menu to check in and out from his session')
|
||||
|
||||
_sql_constraints = [('barcode_uniq', 'unique (barcode)', "The Badge ID must be unique, this one is already assigned to another employee.")]
|
||||
|
||||
@@ -38,7 +38,7 @@ class HrEmployee(models.Model):
|
||||
|
||||
@api.multi
|
||||
def _inverse_manual_attendance(self):
|
||||
manual_attendance_group = self.env['res.groups'].search([('name', '=', 'Manual Attendances')])
|
||||
manual_attendance_group = self.env.ref('hr.group_hr_attendance')
|
||||
for employee in self:
|
||||
if employee.user_id:
|
||||
if employee.manual_attendance:
|
||||
@@ -53,8 +53,6 @@ class HrEmployee(models.Model):
|
||||
|
||||
@api.depends('last_attendance_id.check_in', 'last_attendance_id.check_out', 'last_attendance_id')
|
||||
def _compute_attendance_state(self):
|
||||
"""compute employee state, last check in and last check out
|
||||
"""
|
||||
for employee in self:
|
||||
employee.attendance_state = employee.last_attendance_id and not employee.last_attendance_id.check_out and 'checked_in' or 'checked_out'
|
||||
|
||||
@@ -66,11 +64,11 @@ class HrEmployee(models.Model):
|
||||
|
||||
@api.model
|
||||
def attendance_scan(self, barcode):
|
||||
""" Receive a barcode scanned from the main menu and change the attendances of corresponding employee.
|
||||
""" Receive a barcode scanned from the Kiosk Mode and change the attendances of corresponding employee.
|
||||
Returns either an action or a warning.
|
||||
"""
|
||||
employee = self.search([('barcode', '=', barcode)], limit=1)
|
||||
return employee and employee.attendance_action('hr_attendance.hr_attendance_action_main_menu') or \
|
||||
return employee and employee.attendance_action('hr_attendance.hr_attendance_action_kiosk_mode') or \
|
||||
{'warning': _('No employee corresponding to barcode %(barcode)s') % {'barcode': barcode}}
|
||||
|
||||
@api.multi
|
||||
@@ -85,10 +83,10 @@ class HrEmployee(models.Model):
|
||||
def attendance_action(self, next_action):
|
||||
""" Changes the attendance of the employee.
|
||||
Returns an action to the check in/out message,
|
||||
next_action defines which menu the check in/out message should return to.
|
||||
next_action defines which menu the check in/out message should return to. ("My Attendances" or "Kiosk Mode")
|
||||
"""
|
||||
self.ensure_one()
|
||||
action_message = self.env.ref('hr_attendance.hr_attendance_action_message').read()[0]
|
||||
action_message = self.env.ref('hr_attendance.hr_attendance_action_greeting_message').read()[0]
|
||||
action_message['previous_attendance_change_date'] = self.last_attendance_id and (self.last_attendance_id.check_out or self.last_attendance_id.check_in) or False
|
||||
if action_message['previous_attendance_change_date']:
|
||||
action_message['previous_attendance_change_date'] = \
|
||||
@@ -113,11 +111,7 @@ class HrEmployee(models.Model):
|
||||
raise exceptions.UserError(_('Cannot perform check in or check out on multiple employees.'))
|
||||
action_date = fields.Datetime.now()
|
||||
|
||||
action = 'check_in'
|
||||
if self.attendance_state == 'checked_in':
|
||||
action = 'check_out'
|
||||
|
||||
if action == 'check_in':
|
||||
if self.attendance_state != 'checked_in':
|
||||
vals = {
|
||||
'employee_id': self.id,
|
||||
'check_in': action_date,
|
||||
|
||||
@@ -7,6 +7,6 @@ from odoo import fields, models
|
||||
class BaseConfigSettings(models.TransientModel):
|
||||
_inherit = 'base.config.settings'
|
||||
|
||||
group_attendance_use_pin = fields.Selection([(0, 'Employees do not need to enter their PIN to check in manually in the "Company Screen".'),
|
||||
(1, 'Employees must enter their PIN to check in manually in the "Company Screen".')],
|
||||
group_attendance_use_pin = fields.Selection([(0, 'Employees do not need to enter their PIN to check in manually in the "Kiosk Mode".'),
|
||||
(1, 'Employees must enter their PIN to check in manually in the "Kiosk Mode".')],
|
||||
string='Employee PIN', help='Enable or disable employee PIN identification at check in', implied_group="hr_attendance.group_hr_attendance_use_pin")
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
</record>
|
||||
|
||||
<record id="hr.group_hr_attendance" model="res.groups">
|
||||
<field name="name">Manual Attendances</field>
|
||||
<field name="name">Manual Attendance</field>
|
||||
<field name="category_id" ref="base.module_category_hr_attendance"/>
|
||||
<field name="comment">The user will gain access to the human resources attendance menu, enabling him to manage his own attendance.</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ KanbanRecord.include({
|
||||
var action = {
|
||||
type: 'ir.actions.client',
|
||||
name: 'Confirm',
|
||||
tag: 'hr_attendance_main_menu_confirm',
|
||||
tag: 'hr_attendance_kiosk_confirm',
|
||||
employee_id: this.record.id.raw_value,
|
||||
employee_name: this.record.name.raw_value,
|
||||
employee_state: this.record.attendance_state.raw_value,
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
odoo.define('hr_attendance.Message', function (require) {
|
||||
odoo.define('hr_attendance.greeting_message', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
@@ -8,8 +8,8 @@ var Widget = require('web.Widget');
|
||||
var _t = core._t;
|
||||
|
||||
|
||||
var Message = Widget.extend({
|
||||
template: 'HrAttendanceMessage',
|
||||
var GreetingMessage = Widget.extend({
|
||||
template: 'HrAttendanceGreetingMessage',
|
||||
|
||||
events: {
|
||||
"click .o_hr_attendance_button_dismiss": function () { this.do_action(this.next_action, {clear_breadcrumbs: true}); },
|
||||
@@ -18,11 +18,11 @@ var Message = Widget.extend({
|
||||
init: function(parent, action) {
|
||||
this._super.apply(this, arguments);
|
||||
var self = this;
|
||||
var default_action = { type: "ir.actions.client", name: 'Attendances', tag: 'hr_attendance_my_main_menu', params: {} };
|
||||
var default_action = { type: "ir.actions.client", name: 'Attendances', tag: 'hr_attendance_my_attendances', params: {} };
|
||||
if(!action.attendance){
|
||||
self.session.user_has_group('hr_attendance.group_hr_attendance_user').then(function(has_group){
|
||||
if(has_group){
|
||||
self.next_action = { type: "ir.actions.client", name: 'Attendances', tag: 'hr_attendance_main_menu', target: 'fullscreen', params: {} };
|
||||
self.next_action = { type: "ir.actions.client", name: 'Attendances', tag: 'hr_attendance_kiosk_mode', target: 'fullscreen', params: {} };
|
||||
}
|
||||
});
|
||||
return;
|
||||
@@ -124,8 +124,8 @@ var Message = Widget.extend({
|
||||
},
|
||||
});
|
||||
|
||||
core.action_registry.add('hr_attendance_message', Message);
|
||||
core.action_registry.add('hr_attendance_greeting_message', GreetingMessage);
|
||||
|
||||
return Message;
|
||||
return GreetingMessage;
|
||||
|
||||
});
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
odoo.define('hr_attendance.MainMenuConfirm', function (require) {
|
||||
odoo.define('hr_attendance.kiosk_confirm', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
@@ -9,7 +9,7 @@ var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
|
||||
var MainMenuConfirm = Widget.extend({
|
||||
var KioskConfirm = Widget.extend({
|
||||
events: {
|
||||
"click .o_hr_attendance_back_button": function () { this.do_action(this.next_action, {clear_breadcrumbs: true}); },
|
||||
"click .o_hr_attendance_sign_in_out_icon": function () {
|
||||
@@ -55,7 +55,7 @@ var MainMenuConfirm = Widget.extend({
|
||||
|
||||
init: function (parent, action) {
|
||||
this._super.apply(this, arguments);
|
||||
this.next_action = { type: "ir.actions.client", name: "Attendances", tag: 'hr_attendance_main_menu', target: "fullscreen", params: {} };
|
||||
this.next_action = { type: "ir.actions.client", name: "Attendances", tag: 'hr_attendance_kiosk_mode', target: "fullscreen", params: {} };
|
||||
this.employee_id = action.employee_id;
|
||||
this.employee_name = action.employee_name;
|
||||
this.employee_state = action.employee_state;
|
||||
@@ -66,7 +66,7 @@ var MainMenuConfirm = Widget.extend({
|
||||
var self = this;
|
||||
self.session.user_has_group('hr_attendance.group_hr_attendance_use_pin').then(function(has_group){
|
||||
self.use_pin = has_group;
|
||||
self.$el.html(QWeb.render("HrAttendanceMainMenuConfirm", {widget: self}));
|
||||
self.$el.html(QWeb.render("HrAttendanceKioskConfirm", {widget: self}));
|
||||
self.start_clock();
|
||||
});
|
||||
return self._super.apply(this, arguments);
|
||||
@@ -84,8 +84,8 @@ var MainMenuConfirm = Widget.extend({
|
||||
},
|
||||
});
|
||||
|
||||
core.action_registry.add('hr_attendance_main_menu_confirm', MainMenuConfirm);
|
||||
core.action_registry.add('hr_attendance_kiosk_confirm', KioskConfirm);
|
||||
|
||||
return MainMenuConfirm;
|
||||
return KioskConfirm;
|
||||
|
||||
});
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
odoo.define('hr_attendance.MainMenu', function (require) {
|
||||
odoo.define('hr_attendance.kiosk_mode', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
@@ -11,7 +11,7 @@ var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
|
||||
var MainMenu = Widget.extend(BarcodeHandlerMixin, {
|
||||
var KioskMode = Widget.extend(BarcodeHandlerMixin, {
|
||||
events: {
|
||||
"click .o_hr_attendance_button_employees": function(){ this.do_action('hr_attendance.hr_employee_attendance_action_kanban'); },
|
||||
},
|
||||
@@ -33,7 +33,7 @@ var MainMenu = Widget.extend(BarcodeHandlerMixin, {
|
||||
.then(function (companies){
|
||||
self.company_name = companies[0].name;
|
||||
self.company_image_url = self.session.url('/web/image', {model: 'res.company', id: self.session.company_id, field: 'logo',})
|
||||
self.$el.html(QWeb.render("HrAttendanceMainMenu", {widget: self}));
|
||||
self.$el.html(QWeb.render("HrAttendanceKioskMode", {widget: self}));
|
||||
self.start_clock();
|
||||
});
|
||||
return self._super.apply(this, arguments);
|
||||
@@ -64,8 +64,8 @@ var MainMenu = Widget.extend(BarcodeHandlerMixin, {
|
||||
},
|
||||
});
|
||||
|
||||
core.action_registry.add('hr_attendance_main_menu', MainMenu);
|
||||
core.action_registry.add('hr_attendance_kiosk_mode', KioskMode);
|
||||
|
||||
return MainMenu;
|
||||
return KioskMode;
|
||||
|
||||
});
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
odoo.define('hr_attendance.MyMainMenu', function (require) {
|
||||
odoo.define('hr_attendance.my_attendances', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
@@ -9,7 +9,7 @@ var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
|
||||
var MyMainMenu = Widget.extend({
|
||||
var MyAttendances = Widget.extend({
|
||||
events: {
|
||||
"click .o_hr_attendance_sign_in_out_icon": function() {
|
||||
this.$('.o_hr_attendance_sign_in_out_icon').attr("disabled", "disabled");
|
||||
@@ -39,7 +39,7 @@ var MyMainMenu = Widget.extend({
|
||||
update_attendance: function () {
|
||||
var self = this;
|
||||
var hr_employee = new Model('hr.employee');
|
||||
hr_employee.call('attendance_manual', [[self.employee.id], 'hr_attendance.hr_attendance_action_my_main_menu'])
|
||||
hr_employee.call('attendance_manual', [[self.employee.id], 'hr_attendance.hr_attendance_action_my_attendances'])
|
||||
.then(function(result) {
|
||||
if (result.action) {
|
||||
self.do_action(result.action);
|
||||
@@ -50,8 +50,8 @@ var MyMainMenu = Widget.extend({
|
||||
},
|
||||
});
|
||||
|
||||
core.action_registry.add('hr_attendance_my_main_menu', MyMainMenu);
|
||||
core.action_registry.add('hr_attendance_my_attendances', MyAttendances);
|
||||
|
||||
return MyMainMenu;
|
||||
return MyAttendances;
|
||||
|
||||
});
|
||||
@@ -57,7 +57,7 @@
|
||||
margin: 5px 0px 5px 0px;
|
||||
}
|
||||
|
||||
.o_hr_attendance_main_menu_container {
|
||||
.o_hr_attendance_kiosk_mode_container {
|
||||
.o-flex-display();
|
||||
.o-flex-flow(column, nowrap);
|
||||
.o-justify-content(center);
|
||||
@@ -80,7 +80,7 @@
|
||||
.o_hr_attendance_random_message {
|
||||
font-style: italic;
|
||||
}
|
||||
.o_hr_attendance_main_menu {
|
||||
.o_hr_attendance_kiosk_mode {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="HrAttendanceMainMenu">
|
||||
<div class="o_hr_attendance_main_menu_container">
|
||||
<div class="o_hr_attendance_main_menu">
|
||||
<t t-name="HrAttendanceKioskMode">
|
||||
<div class="o_hr_attendance_kiosk_mode_container">
|
||||
<div class="o_hr_attendance_kiosk_mode">
|
||||
<h1>Welcome to <t t-esc="widget.company_name"/></h1>
|
||||
<img t-attf-src="{{widget.company_image_url}}" alt="Company Logo"/>
|
||||
<h2>Scan your badge</h2>
|
||||
@@ -19,8 +19,8 @@
|
||||
</t>
|
||||
|
||||
<t t-name="HrAttendanceMyMainMenu">
|
||||
<div class="o_hr_attendance_main_menu_container">
|
||||
<div class="o_hr_attendance_main_menu">
|
||||
<div class="o_hr_attendance_kiosk_mode_container">
|
||||
<div class="o_hr_attendance_kiosk_mode">
|
||||
<t t-if="widget.employee">
|
||||
<h1>Welcome <t t-esc="widget.employee.name"/></h1>
|
||||
<t t-if="widget.employee.attendance_state=='checked_out'">
|
||||
@@ -39,9 +39,9 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="HrAttendanceMainMenuConfirm">
|
||||
<div class="o_hr_attendance_main_menu_container">
|
||||
<div class="o_hr_attendance_main_menu">
|
||||
<t t-name="HrAttendanceKioskConfirm">
|
||||
<div class="o_hr_attendance_kiosk_mode_container">
|
||||
<div class="o_hr_attendance_kiosk_mode">
|
||||
<t t-if="widget.employee_id">
|
||||
<h1>Welcome <t t-esc="widget.employee_name"/></h1>
|
||||
<t t-if="!widget.use_pin">
|
||||
@@ -89,7 +89,7 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="HrAttendanceMessage">
|
||||
<t t-name="HrAttendanceGreetingMessage">
|
||||
<div class="o_hr_attendance_message_container">
|
||||
<div class="o_hr_attendance_message">
|
||||
<t t-if="widget.attendance">
|
||||
|
||||
@@ -86,7 +86,10 @@
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{"search_default_today":1}</field>
|
||||
<field name="search_view_id" ref="hr_attendance_view_filter" />
|
||||
<field name="help">The Time Tracking functionality aims to manage employee attendances from Check in/Check out actions. You can also link this feature to an attendance device using Odoo's web service features.</field>
|
||||
<field name="help" type="html">
|
||||
<p>The attendance records of your employees will be displayed here.</p>
|
||||
<p>Please make sure you're using the correct filter if you expected to see any.</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_attendance_action_graph" model="ir.actions.act_window">
|
||||
@@ -109,28 +112,28 @@
|
||||
<field name="view_id"></field> <!-- force empty -->
|
||||
</record>
|
||||
|
||||
<record id="hr_attendance_action_main_menu" model="ir.actions.client">
|
||||
<record id="hr_attendance_action_kiosk_mode" model="ir.actions.client">
|
||||
<field name="name">Attendances</field>
|
||||
<field name="tag">hr_attendance_main_menu</field>
|
||||
<field name="tag">hr_attendance_kiosk_mode</field>
|
||||
<field name="target">fullscreen</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_attendance_action_my_main_menu" model="ir.actions.client">
|
||||
<record id="hr_attendance_action_my_attendances" model="ir.actions.client">
|
||||
<field name="name">Attendance</field>
|
||||
<field name="tag">hr_attendance_my_main_menu</field>
|
||||
<field name="tag">hr_attendance_my_attendances</field>
|
||||
<field name="target">main</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_attendance_action_message" model="ir.actions.client">
|
||||
<record id="hr_attendance_action_greeting_message" model="ir.actions.client">
|
||||
<field name="name">Message</field>
|
||||
<field name="tag">hr_attendance_message</field>
|
||||
<field name="tag">hr_attendance_greeting_message</field>
|
||||
</record>
|
||||
|
||||
<!-- Menus -->
|
||||
|
||||
<menuitem id="menu_hr_attendance_root" name="Attendances" sequence="90" groups="hr.group_hr_attendance" web_icon="hr_attendance,static/description/icon.png"/>
|
||||
|
||||
<menuitem id="menu_hr_attendance_my_attendances" name="My Attendances" parent="menu_hr_attendance_root" sequence="10" groups="hr.group_hr_attendance" action="hr_attendance_action_my_main_menu"/>
|
||||
<menuitem id="menu_hr_attendance_my_attendances" name="My Attendances" parent="menu_hr_attendance_root" sequence="10" groups="hr.group_hr_attendance" action="hr_attendance_action_my_attendances"/>
|
||||
|
||||
<menuitem id="menu_hr_attendance_manage_attendances" name="Manage Attendances" parent="menu_hr_attendance_root" sequence="20" groups="hr_attendance.group_hr_attendance_user"/>
|
||||
|
||||
@@ -138,7 +141,7 @@
|
||||
|
||||
<menuitem id="menu_hr_attendance_view_employees_kanban" name="Employees" parent="menu_hr_attendance_manage_attendances" sequence="15" groups="hr_attendance.group_hr_attendance_user" action="hr.open_view_employee_list_my"/>
|
||||
|
||||
<menuitem id="menu_hr_attendance_Kiosk_mode" name="Kiosk Mode" parent="menu_hr_attendance_manage_attendances" sequence="20" groups="hr_attendance.group_hr_attendance_user" action="hr_attendance_action_main_menu"/>
|
||||
<menuitem id="menu_hr_attendance_kiosk_mode" name="Kiosk Mode" parent="menu_hr_attendance_manage_attendances" sequence="20" groups="hr_attendance.group_hr_attendance_user" action="hr_attendance_action_kiosk_mode"/>
|
||||
|
||||
<menuitem id="menu_hr_attendance_report" name="Reports" parent="menu_hr_attendance_root" sequence="30" groups="hr_attendance.group_hr_attendance_user" action="hr_attendance_action_graph"/>
|
||||
</odoo>
|
||||
|
||||
@@ -82,6 +82,14 @@
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="view_id" ref="hr_employees_view_kanban"/>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
Create a few employees to be able to select an employee here and perform his check in / check out.
|
||||
</p>
|
||||
<p>
|
||||
To create employees go to the Employees menu.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -4,10 +4,10 @@
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/hr_attendance/static/src/js/employee_kanban_view_handler.js"></script>
|
||||
<script type="text/javascript" src="/hr_attendance/static/src/js/presence_widgets.js"></script>
|
||||
<script type="text/javascript" src="/hr_attendance/static/src/js/message.js"></script>
|
||||
<script type="text/javascript" src="/hr_attendance/static/src/js/main_menu.js"></script>
|
||||
<script type="text/javascript" src="/hr_attendance/static/src/js/main_menu_confirm.js"></script>
|
||||
<script type="text/javascript" src="/hr_attendance/static/src/js/my_main_menu.js"></script>
|
||||
<script type="text/javascript" src="/hr_attendance/static/src/js/greeting_message.js"></script>
|
||||
<script type="text/javascript" src="/hr_attendance/static/src/js/kiosk_mode.js"></script>
|
||||
<script type="text/javascript" src="/hr_attendance/static/src/js/kiosk_confirm.js"></script>
|
||||
<script type="text/javascript" src="/hr_attendance/static/src/js/my_attendances.js"></script>
|
||||
<link rel="stylesheet" href="/hr_attendance/static/src/less/hr_attendance.less"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -339,7 +339,8 @@ class HrExpense(models.Model):
|
||||
'product_id': product.id,
|
||||
'product_uom_id': product.uom_id.id,
|
||||
'quantity': 1,
|
||||
'unit_amount': price
|
||||
'unit_amount': price,
|
||||
'company_id': employee.company_id.id,
|
||||
})
|
||||
return super(HrExpense, self).message_new(msg_dict, custom_values)
|
||||
|
||||
@@ -348,7 +349,7 @@ class HrExpenseSheet(models.Model):
|
||||
_name = "hr.expense.sheet"
|
||||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
_description = "Expense Report"
|
||||
_order = "accounting_date desc"
|
||||
_order = "accounting_date desc, id desc"
|
||||
|
||||
name = fields.Char(string='Expense Report Summary', required=True)
|
||||
expense_line_ids = fields.One2many('hr.expense', 'sheet_id', string='Expense Lines', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, copy=False)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Online Expenses Management</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Online Expenses Management</h2>
|
||||
<h3 class="oe_slogan">Easily Manage Employees' Expenses</h3>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Save time on expense records</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Save time on expense records</h2>
|
||||
<h3 class="oe_slogan">Everything at one place</h3>
|
||||
<p class="oe_mt32">
|
||||
Manage your employees' daily expenses. Whether it's travel expenses or any other costs, access all your employees' fee notes and complete, validate or refuse them. No need to download any specialized software or program to manage expenses - everything can be done directly in the app!
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Stop losing receipts</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Stop losing receipts</h2>
|
||||
<h3 class="oe_slogan">Upload all receipts within expense records</h3>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt16 text-justify">
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Manage expenses per team</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Manage expenses per team</h2>
|
||||
<h3 class="oe_slogan">Have a clear overview of a team's spendings</h3>
|
||||
<p class="oe_mt16 oe_mb32 text-center">
|
||||
As a manager, follow expenses recorded by a full team to keep an eye on the costs and make sure to keep to the target and budget.
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Share the workload between departments</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Share the workload between departments</h2>
|
||||
<h3 class="oe_slogan oe_mb32">Get everyone involved to save time</h3>
|
||||
<div class="oe_span4">
|
||||
<img class="center-block" src="employees.png">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import controllers
|
||||
import models
|
||||
import report
|
||||
import wizard
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*
|
||||
import main
|
||||
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.controllers.main import MailController
|
||||
from odoo import http
|
||||
|
||||
|
||||
class HrHolidaysController(http.Controller):
|
||||
|
||||
@http.route('/hr_holidays/validate', type='http', auth='user', methods=['GET'])
|
||||
def hr_holidays_validate(self, res_id, token):
|
||||
comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.holidays', int(res_id), token)
|
||||
if comparison and record:
|
||||
try:
|
||||
record.signal_workflow('validate')
|
||||
except Exception:
|
||||
return MailController._redirect_to_messaging()
|
||||
return redirect
|
||||
|
||||
@http.route('/hr_holidays/refuse', type='http', auth='user', methods=['GET'])
|
||||
def hr_holidays_refuse(self, res_id, token):
|
||||
comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.holidays', int(res_id), token)
|
||||
if comparison and record:
|
||||
try:
|
||||
record.signal_workflow('refuse')
|
||||
except Exception:
|
||||
return MailController._redirect_to_messaging()
|
||||
return redirect
|
||||
@@ -560,10 +560,10 @@ class Holidays(models.Model):
|
||||
|
||||
self.ensure_one()
|
||||
if self.state == 'confirm':
|
||||
app_action = self._notification_link_helper('method', method='action_validate')
|
||||
app_action = self._notification_link_helper('controller', controller='/hr_holidays/validate')
|
||||
hr_actions = [{'url': app_action, 'title': _('Approve')}]
|
||||
if self.state in ['confirm', 'validate', 'validate1']:
|
||||
ref_action = self._notification_link_helper('method', method='action_refuse')
|
||||
ref_action = self._notification_link_helper('controller', controller='/hr_holidays/refuse')
|
||||
hr_actions = [{'url': ref_action, 'title': _('Refuse')}]
|
||||
|
||||
new_group = (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Leaves management for all</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Leaves management for all</h2>
|
||||
<h3 class="oe_slogan">Manage employee vacations & absence</h3>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Manage employee leaves</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Manage employee leaves</h2>
|
||||
<h3 class="oe_slogan">Keep track of all your employees vacations</h3>
|
||||
<div class="oe_span6 text-justify">
|
||||
<p class="oe_mt32">
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Approve or refuse leave requests</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Approve or refuse leave requests</h2>
|
||||
<h3 class="oe_slogan">Handle all requests from your employees</h3>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="leaves_sc_02.png">
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Get reports to plan ahead</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Get reports to plan ahead</h2>
|
||||
<h3 class="oe_slogan">Simple reporting tool</h3>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32 text-justify">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Hiring Processes Made Easy</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Hiring Processes Made Easy</h2>
|
||||
<h3 class="oe_slogan">From the sourcing to the contract, handle your recruitment process easily</h3>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Manage your hiring process</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Manage your hiring process</h2>
|
||||
<h3 class="oe_slogan">Organize your vacancies job applications</h3>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture" src="hr_recruitment_sc_01.png">
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Track job offers straight from the app</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Track job offers straight from the app</h2>
|
||||
<h3 class="oe_slogan oe_mb32">See which channel drives most applications and gather all of them in Odoo.</h3>
|
||||
<div class="oe_span6 text-center">
|
||||
<span class="fa fa-file-text fa-2x"/>
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Customize your recruitment process</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Customize your recruitment process</h2>
|
||||
<h3 class="oe_slogan">Define your own stages and interviewers</h3>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="job_applications.png">
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Simplify application management</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Simplify application management</h2>
|
||||
<h3 class="oe_slogan">Index resumes, track applicants, search profiles</h3>
|
||||
<div class="oe_span6">
|
||||
<p class='oe_mt32 text-justify'>
|
||||
@@ -94,7 +94,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Integrated Surveys</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Integrated Surveys</h2>
|
||||
<h3 class="oe_slogan oe_mb32">Define your own online/offline surveys</h3>
|
||||
<div class="oe_span4 text-center">
|
||||
<span class="fa fa-file-text fa-2x"/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Track time, Forecast productivity</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Track time, Forecast productivity</h2>
|
||||
<h3 class="oe_slogan">Get things done as fast as you can think.</h3>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">It supports your own work flow</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">It supports your own work flow</h2>
|
||||
<h3 class="oe_slogan oe_mb32">Getting Things Done. Timer. Weekly Timesheet.</h3>
|
||||
<div class="oe_span4">
|
||||
<img class="oe_picture oe_screenshot" src="hr_timesheet_sheet_sc_01a.png">
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Enjoy Timesheet anywhere. Even offline.</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Enjoy Timesheet anywhere. Even offline.</h2>
|
||||
<h3 class="oe_slogan">The mobile app that adapts to your work</h3>
|
||||
<p class="oe_mt32">
|
||||
Whether you 're a small business, a freelance or a professional managing employees from different locations, you need your apps to be as mobile as you are. Log in and track your time through any device.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Answer your customers questions live</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Answer your customers questions live</h2>
|
||||
<h3 class="oe_slogan">Engage with visitors, deliver real time support to customers</h3>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Do it live!</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Do it live!</h2>
|
||||
<h3 class="oe_slogan">Convert more visitors into leads and customers</h3>
|
||||
<div class="col-md-5 oe_mt32 text-justify">
|
||||
<span class="fa fa-eyedropper fa-2x pull-left"/>
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Select your audience</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Select your audience</h2>
|
||||
<h3 class="oe_slogan">Provide direct chat to a specified target</h3>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture" src="im_livechat_sc_02.png">
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Accessible from anywhere</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Accessible from anywhere</h2>
|
||||
<h3 class="oe_slogan">Chat from within any module</h3>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32 text-justify">
|
||||
@@ -68,7 +68,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Improve your level of service</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Improve your level of service</h2>
|
||||
<h3 class="oe_slogan">Make sure to keep customers satisfied</h3>
|
||||
<div class="oe_span3">
|
||||
<img class="oe_picture oe_screenshot" src="im_livechat_02.png"/>
|
||||
@@ -86,7 +86,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Answer more quickly with templates</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Answer more quickly with templates</h2>
|
||||
<h3 class="oe_slogan">Use pre-written answers to be faster</h3>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32 text-justify">
|
||||
|
||||
+105
-101
@@ -3,26 +3,96 @@
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import psycopg2
|
||||
import werkzeug
|
||||
|
||||
from operator import itemgetter
|
||||
from werkzeug import url_encode
|
||||
|
||||
from odoo import api, http, registry, SUPERUSER_ID
|
||||
from odoo import api, http, registry, SUPERUSER_ID, _
|
||||
from odoo.addons.web.controllers.main import binary_content
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.http import request
|
||||
from odoo.tools import consteq
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MailController(http.Controller):
|
||||
_cp_path = '/mail'
|
||||
|
||||
def _redirect_to_messaging(self):
|
||||
@classmethod
|
||||
def _redirect_to_messaging(cls):
|
||||
messaging_action = request.env['mail.thread']._get_inbox_action_xml_id()
|
||||
url = '/web#%s' % url_encode({'action': messaging_action})
|
||||
return werkzeug.utils.redirect(url)
|
||||
|
||||
@classmethod
|
||||
def _check_token(cls, token):
|
||||
base_link = request.httprequest.path
|
||||
params = dict(request.params)
|
||||
params.pop('token', '')
|
||||
valid_token = request.env['mail.thread']._generate_notification_token(base_link, params)
|
||||
return consteq(valid_token, str(token))
|
||||
|
||||
@classmethod
|
||||
def _check_token_and_record_or_redirect(cls, model, res_id, token):
|
||||
comparison = cls._check_token(token)
|
||||
if not comparison:
|
||||
_logger.warning(_('Invalid token in route %s') % request.httprequest.url)
|
||||
return comparison, None, cls._redirect_to_messaging()
|
||||
try:
|
||||
record = request.env[model].browse(res_id).exists()
|
||||
except Exception:
|
||||
record = None
|
||||
redirect = cls._redirect_to_messaging()
|
||||
else:
|
||||
redirect = cls._redirect_to_record(model, res_id)
|
||||
return comparison, record, redirect
|
||||
|
||||
@classmethod
|
||||
def _redirect_to_record(cls, model, res_id):
|
||||
uid = request.session.uid
|
||||
|
||||
# no model / res_id, meaning no possible record -> redirect to login
|
||||
if not model or not res_id or model not in request.env:
|
||||
return cls._redirect_to_messaging()
|
||||
|
||||
# find the access action using sudo to have the details about the access link
|
||||
RecordModel = request.env[model]
|
||||
record_sudo = RecordModel.sudo().browse(res_id).exists()
|
||||
if not record_sudo:
|
||||
# record does not seem to exist -> redirect to login
|
||||
return cls._redirect_to_messaging()
|
||||
record_action = record_sudo.get_access_action()
|
||||
|
||||
# the record has an URL redirection: use it directly
|
||||
if record_action['type'] == 'ir.actions.act_url':
|
||||
return werkzeug.utils.redirect(record_action['url'])
|
||||
# other choice: act_window (no support of anything else currently)
|
||||
elif not record_action['type'] == 'ir.actions.act_window':
|
||||
return cls._redirect_to_messaging()
|
||||
|
||||
# the record has a window redirection: check access rights
|
||||
if not RecordModel.sudo(uid).check_access_rights('read', raise_exception=False):
|
||||
return cls._redirect_to_messaging()
|
||||
try:
|
||||
record_sudo.sudo(uid).check_access_rule('read')
|
||||
except AccessError:
|
||||
return cls._redirect_to_messaging()
|
||||
|
||||
url_params = {
|
||||
'view_type': record_action['view_type'],
|
||||
'model': model,
|
||||
'id': res_id,
|
||||
'active_id': res_id,
|
||||
'view_id': record_sudo.get_formview_id(),
|
||||
'action': record_action.get('id'),
|
||||
}
|
||||
url = '/web?#%s' % url_encode(url_params)
|
||||
return werkzeug.utils.redirect(url)
|
||||
|
||||
@http.route('/mail/receive', type='json', auth='none')
|
||||
def receive(self, req):
|
||||
""" End-point to receive mail from an external SMTP server. """
|
||||
@@ -94,8 +164,6 @@ class MailController(http.Controller):
|
||||
- users without read access are redirected to the Messaging
|
||||
- not logged users are redirected to the login page
|
||||
"""
|
||||
uid = request.session.uid
|
||||
|
||||
if message_id:
|
||||
try:
|
||||
message = request.env['mail.message'].sudo().browse(int(message_id)).exists()
|
||||
@@ -109,112 +177,48 @@ class MailController(http.Controller):
|
||||
elif res_id and isinstance(res_id, basestring):
|
||||
res_id = int(res_id)
|
||||
|
||||
# no model / res_id, meaning no possible record -> redirect to login
|
||||
if not model or not res_id or model not in request.env:
|
||||
return self._redirect_to_messaging()
|
||||
return self._redirect_to_record(model, res_id)
|
||||
|
||||
# find the access action using sudo to have the details about the access link
|
||||
RecordModel = request.env[model].sudo(uid)
|
||||
record = RecordModel.browse(res_id).exists()
|
||||
if not record:
|
||||
# record does not seem to exist -> redirect to login
|
||||
return self._redirect_to_messaging()
|
||||
record_action = record.get_access_action()
|
||||
@http.route('/mail/follow', type='http', auth='user', methods=['GET'])
|
||||
def mail_action_follow(self, model, res_id, token=None):
|
||||
comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token)
|
||||
if comparison and record:
|
||||
try:
|
||||
record.sudo().message_subscribe_users()
|
||||
except Exception:
|
||||
return self._redirect_to_messaging()
|
||||
return redirect
|
||||
|
||||
# only URL redirections or window actions supported currently
|
||||
if not record_action['type'] in ('ir.actions.act_url', 'ir.actions.act_window'):
|
||||
return self._redirect_to_messaging()
|
||||
|
||||
# the record has an URL redirection: use it directly
|
||||
if record_action['type'] == 'ir.actions.act_url':
|
||||
return werkzeug.utils.redirect(record_action['url'])
|
||||
|
||||
# the record has a window redirection: check access rights
|
||||
if not RecordModel.check_access_rights('read', raise_exception=False):
|
||||
return self._redirect_to_messaging()
|
||||
try:
|
||||
record.check_access_rule('read')
|
||||
except AccessError:
|
||||
return self._redirect_to_messaging()
|
||||
|
||||
# at this point user can read the document so no issue with get_formview_id
|
||||
query = {}
|
||||
url_params = {
|
||||
'view_type': record_action['view_type'],
|
||||
'model': model,
|
||||
'id': res_id,
|
||||
'active_id': res_id,
|
||||
'view_id': record.get_formview_id(),
|
||||
'action': record_action.get('id'),
|
||||
}
|
||||
url = '/web?%s#%s' % (url_encode(query), url_encode(url_params))
|
||||
return werkzeug.utils.redirect(url)
|
||||
|
||||
@http.route('/mail/follow', type='http', auth='user')
|
||||
def mail_action_follow(self, model, res_id):
|
||||
if model not in request.env:
|
||||
return self._redirect_to_messaging()
|
||||
Model = request.env[model]
|
||||
try:
|
||||
Model.browse(res_id).message_subscribe_users()
|
||||
except:
|
||||
return self._redirect_to_messaging()
|
||||
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
|
||||
|
||||
@http.route('/mail/unfollow', type='http', auth='user')
|
||||
def mail_action_unfollow(self, model, res_id):
|
||||
if model not in request.env:
|
||||
return self._redirect_to_messaging()
|
||||
Model = request.env[model]
|
||||
try:
|
||||
Model.browse(res_id).sudo().message_unsubscribe_users([request.uid])
|
||||
except:
|
||||
return self._redirect_to_messaging()
|
||||
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
|
||||
@http.route('/mail/unfollow', type='http', auth='user', methods=['GET'])
|
||||
def mail_action_unfollow(self, model, res_id, token=None):
|
||||
comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token)
|
||||
if comparison and record:
|
||||
try:
|
||||
# TDE CHECKME: is sudo really necessary ?
|
||||
record.sudo().message_unsubscribe_users([request.uid])
|
||||
except Exception:
|
||||
return self._redirect_to_messaging()
|
||||
return redirect
|
||||
|
||||
@http.route('/mail/new', type='http', auth='user')
|
||||
def mail_action_new(self, model, res_id, **kwargs):
|
||||
def mail_action_new(self, model, res_id, action_id):
|
||||
if model not in request.env:
|
||||
return self._redirect_to_messaging()
|
||||
params = {'view_type': 'form', 'model': model}
|
||||
if kwargs.get('action_id'):
|
||||
params['action'] = kwargs['action_id']
|
||||
if action_id:
|
||||
# Probably something to do
|
||||
params['action'] = action_id
|
||||
return werkzeug.utils.redirect('/web?#%s' % url_encode(params))
|
||||
|
||||
@http.route('/mail/method', type='http', auth='user')
|
||||
def mail_action_method(self, model, res_id, method, **kwargs):
|
||||
# only public methods / check exists
|
||||
if method.strip().startswith('_') or model not in request.env:
|
||||
return self._redirect_to_messaging()
|
||||
Model = request.env[model]
|
||||
try:
|
||||
record = Model.browse(int(res_id)).exists()
|
||||
getattr(record, method)(**json.loads(kwargs.get('params', {})))
|
||||
except:
|
||||
return self._redirect_to_messaging()
|
||||
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
|
||||
|
||||
@http.route('/mail/assign', type='http', auth='user')
|
||||
def mail_action_assign(self, model, res_id):
|
||||
if model not in request.env:
|
||||
return self._redirect_to_messaging()
|
||||
Model = request.env[model]
|
||||
try:
|
||||
Model.browse(int(res_id)).exists().write({'user_id': request.uid})
|
||||
except:
|
||||
return self._redirect_to_messaging()
|
||||
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
|
||||
|
||||
@http.route('/mail/workflow', type='http', auth='user')
|
||||
def mail_action_workflow(self, model, res_id, signal, **kwargs):
|
||||
if model not in request.env:
|
||||
return self._redirect_to_messaging()
|
||||
Model = request.env[model]
|
||||
try:
|
||||
Model.browse(int(res_id)).exists().signal_workflow(signal)
|
||||
except:
|
||||
return self._redirect_to_messaging()
|
||||
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
|
||||
@http.route('/mail/assign', type='http', auth='user', methods=['GET'])
|
||||
def mail_action_assign(self, model, res_id, token=None):
|
||||
comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token)
|
||||
if comparison and record:
|
||||
try:
|
||||
record.write({'user_id': request.uid})
|
||||
except Exception:
|
||||
return self._redirect_to_messaging()
|
||||
return redirect
|
||||
|
||||
@http.route('/mail/<string:res_model>/<int:res_id>/avatar/<int:partner_id>', type='http', auth='public')
|
||||
def avatar(self, res_model, res_id, partner_id):
|
||||
|
||||
@@ -82,25 +82,25 @@
|
||||
<tbody><tr>
|
||||
<td valign="center">
|
||||
% if ctx.get('has_button_access'):
|
||||
<a href="${ctx['button_access']['url']}" style="padding: 8px 12px; font-size: 12px; color: #FFFFFF; text-decoration: none !important; font-weight: 400; background-color: #a24689; border: 0px solid #a24689; border-radius:3px">${ctx['button_access']['title']}</a>
|
||||
<a href="${ctx['button_access']['url']}" style="padding: 8px 12px; font-size: 12px; color: #FFFFFF; text-decoration: none !important; font-weight: 400; background-color: #875A7B; border: 0px solid #875A7B; border-radius:3px">${ctx['button_access']['title']}</a>
|
||||
% endif
|
||||
|
||||
% if ctx.get('has_button_follow'):
|
||||
% if ctx.get('has_button_access'):
|
||||
|
|
||||
% endif
|
||||
<a href="${ctx['button_follow']['url']}" style="color: #a24689; text-decoration: none !important;">${ctx['button_follow']['title']}</a>
|
||||
<a href="${ctx['button_follow']['url']}" style="color: #875A7B; text-decoration: none !important;">${ctx['button_follow']['title']}</a>
|
||||
% elif ctx.get('has_button_unfollow'):
|
||||
% if ctx.get('has_button_access'):
|
||||
|
|
||||
% endif
|
||||
<a href="${ctx['button_unfollow']['url']}" style="color: #a24689; text-decoration: none !important;">${ctx['button_unfollow']['title']}</a>
|
||||
<a href="${ctx['button_unfollow']['url']}" style="color: #875A7B; text-decoration: none !important;">${ctx['button_unfollow']['title']}</a>
|
||||
% endif
|
||||
|
||||
% if ctx.get('actions'):
|
||||
% for action in ctx['actions']:
|
||||
|
|
||||
<a href="${action['url']}" style="color: #a24689; text-decoration:none !important;">${action['title']}</a>
|
||||
<a href="${action['url']}" style="color: #875A7B; text-decoration:none !important;">${action['title']}</a>
|
||||
% endfor
|
||||
% endif
|
||||
</td>
|
||||
@@ -138,14 +138,14 @@ ${object.body | safe}
|
||||
<p style="color: #555555;">
|
||||
Sent by
|
||||
% if ctx.get('website_url'):
|
||||
<a href="${ctx['website_url']}" style="text-decoration:none; color: #a24689;">
|
||||
<a href="${ctx['website_url']}" style="text-decoration:none; color: #875A7B;">
|
||||
% endif
|
||||
${ctx.get('company_name')}
|
||||
% if ctx.get('website_url'):
|
||||
</a>
|
||||
% endif
|
||||
using
|
||||
<a href="https://www.odoo.com" style="text-decoration:none; color: #a24689;">Odoo</a>.
|
||||
<a href="https://www.odoo.com" style="text-decoration:none; color: #875A7B;">Odoo</a>.
|
||||
</p>
|
||||
</div>]]></field>
|
||||
</record>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
.. _changelog:
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
`trunk (saas-2)`
|
||||
----------------
|
||||
|
||||
- ``mass_mailing_campaign`` update
|
||||
|
||||
- ``mail_mail`: moved ``reply_to`` computation from ``mail_mail`` to ``mail_message``
|
||||
where it belongs, as the field is located onto the ``mail_message`` model.
|
||||
- ``mail_compose_message``: template rendering is now done in batch. Each template
|
||||
is rendered for all res_ids, instead of all templates one id at a time.
|
||||
- ``mail_thread``: to ease inheritance, processing of routes is now done in
|
||||
message_route_process, called in message_route
|
||||
|
||||
- added support of ``active_domain`` form context, coming from the list view.
|
||||
When checking the header hook, the mass mailing will be done on all records
|
||||
matching the ``active_domain``.
|
||||
- added ``mail_server_id`` to mail_message, removing it from mail_mail. This allows
|
||||
to set the preferred mail server to use for notifications emails, when using
|
||||
templates.
|
||||
- added ``_mail_post_access`` attribute that specifies the access right that
|
||||
should have the user in order to post a new message on a given model. Values
|
||||
are ``read`` (portal documents), ``write`` (default value), ``unlink`` or ``create``.
|
||||
@@ -1,12 +0,0 @@
|
||||
Mail module documentation
|
||||
=========================
|
||||
|
||||
The Mail module holds all models and logic related to messages management. At low-level, it handles messages and offers an API to message queues for email sending. At an higher level, it provides the OpenChatter feature that is a thread management system inside OpenERP. A model that inherits from the mail module adds OpenChatter to its document. Its gives them the following capabilities :
|
||||
|
||||
- messages management with a threaded design
|
||||
- subscription mechanism tha allow users to follow/unfollow documents
|
||||
- notifications mechanism; notifications are pushed to users to form a Wall page holding the latest pushed messages
|
||||
|
||||
The mail module also comes with an email composition wizard, along with discussion groups.
|
||||
|
||||
.. include:: index.rst.inc
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
Mail Module documentation topics
|
||||
'''''''''''''''''''''''''''''''''
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
mail_message
|
||||
mail_thread
|
||||
mail_openchatter_howto
|
||||
mail_needaction_howto
|
||||
mail_partner
|
||||
mail_state
|
||||
mail_subtype
|
||||
|
||||
|
||||
Changelog
|
||||
'''''''''
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
changelog.rst
|
||||
@@ -1,111 +0,0 @@
|
||||
.. _mail_message:
|
||||
|
||||
mail.message
|
||||
============
|
||||
|
||||
Models
|
||||
++++++
|
||||
|
||||
``mail.message`` is a class for holding the main attributes of a message object
|
||||
(notification for system message, reciving email message or sent messages). It
|
||||
could be reused as parent model for any database model, wall thread view, or
|
||||
wizard screen that needs to hold a kind of message.
|
||||
|
||||
All internal logic should be in a database-based model while this model
|
||||
holds the basics of a message. For example, a wizard for writing emails
|
||||
should inherit from this class.
|
||||
|
||||
|
||||
.. versionchanged:: 7.0
|
||||
|
||||
ClientAction (ir.actions.client)
|
||||
++++++++++++++++++++++++++++++++
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<record id="mail_message_action_inbox" model="ir.actions.client">
|
||||
<field name="name">Inbox</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="context"></field>
|
||||
<field name="params"></field>
|
||||
<field name="help" type="html"></field>
|
||||
</record>
|
||||
|
||||
- ``tag`` : 'mail.wall', 'mail_thread' or 'mail.widget'
|
||||
'mail.wall' to have a display like the mail wall with threads, title, search view
|
||||
(default value like a wall)
|
||||
'mail_thread' widget for field on standard view. (default value like a thread for
|
||||
record, view on flat mode, no reply, no read/unread)
|
||||
'mail.widget' it's the root thread, used by 'mail.wall' and 'mail_thread'
|
||||
|
||||
- ``help`` : Text HTML to display if there are no message
|
||||
- ``context`` : insert 'default_model' and 'default_res_id'
|
||||
- ``params`` : options for the widget
|
||||
- ``domain`` : choose the domain of the messages
|
||||
- ``truncate_limit`` : {Number} number of character to display before having a "show more"
|
||||
link; note that the text will not be truncated if it does not have 110% of the parameter
|
||||
- ``show_record_name`` : {Boolean} display the name and link of the related record
|
||||
- ``show_reply_button`` : {Boolean} display the reply button
|
||||
- ``show_read_unread_button`` : {Boolean} display the read/unread button
|
||||
- ``display_indented_thread`` : {int [0,1]} number thread level to indented threads.
|
||||
- ``show_compose_message`` : display the composer on top thread
|
||||
- ``show_compact_message`` : display the compact message on the thread when the user clic
|
||||
on this compact mode, the composer is open
|
||||
- ``message_ids`` : {Array | False} List of ids to fetch by the root thread. If no value,
|
||||
the root search the message by the domain
|
||||
- ``compose_placeholder`` : Message to display on the textareaboxes.
|
||||
- ``show_link`` : Display partner (authors, followers...) on link or not
|
||||
- ``compose_as_todo`` : The root composer mark automatically the message as todo
|
||||
- ``readonly`` : Read only mode, hide all action buttons and composer
|
||||
|
||||
Fields
|
||||
++++++
|
||||
|
||||
- ``type`` : usually 'email', 'comment', 'notification'.
|
||||
Message type: email for external email message recieve, notification for system
|
||||
message, comment for other messages such as user replies.
|
||||
- ``subtype_id`` :
|
||||
Subtype of the notification for system message. The users can followe a document
|
||||
and choose the subtype of this document (eg: Create, Comment, Done).
|
||||
You can defined new subtypes and choose his name, by agreement the id begin by "mt\_" on the model
|
||||
"mail.message.subtype".
|
||||
- ``partner_ids`` :
|
||||
List of recipients, the recipients have this message in their personal mailboxe.
|
||||
- ``notified_partner_ids`` :
|
||||
Partners that have a notification pushing this message in their mailboxes. Usualy
|
||||
it's an automatic system message for a model followed by users.
|
||||
- ``notification_ids`` :
|
||||
Technical field holding the message notifications. Use notified_partner_ids to access
|
||||
notified partners.
|
||||
- ``attachment_ids`` :
|
||||
List of attachments
|
||||
- ``parent_id`` :
|
||||
It's the initial thread message. It's use for group message by thread in mailboxes.
|
||||
- ``child_ids`` :
|
||||
List of child message linked to the initial thread message.
|
||||
- ``model`` :
|
||||
Related Document Moded. It's use for group message by document.
|
||||
- ``res_id`` :
|
||||
Related Document ID. It's use for group message by document.
|
||||
- ``record_name`` :
|
||||
Functionnal field use to get the name of related document.
|
||||
- ``vote_user_ids`` :
|
||||
List of partner that vote/like this message.
|
||||
- ``to_read`` :
|
||||
Functional field to search for messages the current user has to read. The messages as
|
||||
treat like a need action. When the user recive a message (to_read = True) this message
|
||||
or action must be performed and mark done (to_read = False)
|
||||
- ``favorite_user_ids`` :
|
||||
Users that set this message in their favorites/todo list.
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
- ``message_read`` :
|
||||
Value: ids, domain, message_unload_ids, thread_level, context, parent_id, limit
|
||||
Return: List of dictinary of message. All message is sent with their parented messages and
|
||||
sort by id. The messages that the user can read but not in his search, are group in
|
||||
expandable messages. The expandable messages contain the domain to expand.
|
||||
- ``check_access_rule`` :
|
||||
Overwrite the initial message for this model.
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
How to use the need action mechanism in my addon
|
||||
=================================================
|
||||
|
||||
Make your module inheriting from ir.needaction_mixin class
|
||||
|
||||
Feature integration
|
||||
++++++++++++++++++++
|
||||
|
||||
::
|
||||
|
||||
class my_module(osv.osv):
|
||||
_name = "my.module"
|
||||
_description = "My Module"
|
||||
_inherit = ['ir.needaction_mixin']
|
||||
@@ -1,112 +0,0 @@
|
||||
|
||||
How to use OpenChatter in my addon
|
||||
===================================
|
||||
|
||||
Running example
|
||||
++++++++++++++++
|
||||
|
||||
A small my_task model will be used as example to explain how to use the
|
||||
OpenChatter feature. Being simple, it has only the following fields:
|
||||
|
||||
- a name
|
||||
- a task responsible
|
||||
- a related project
|
||||
|
||||
::
|
||||
|
||||
class my_task(osv.osv):
|
||||
_name = "my.task"
|
||||
_description = "My Task"
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True, size=64),
|
||||
'user_id':fields.many2one('res.users', string='Responsible',
|
||||
ondelete='cascade', required=True, select=1),
|
||||
'project_id':fields.many2one('project.project', string='Related project',
|
||||
ondelete='cascade', required=True, select=1),
|
||||
}
|
||||
|
||||
|
||||
Two-lines feature integration
|
||||
++++++++++++++++++++++++++++++
|
||||
|
||||
Make your object inherit from :class:`mail.thread`::
|
||||
|
||||
class my_task(osv.osv):
|
||||
_name = "my.task"
|
||||
_description = "My Task"
|
||||
# inherit from mail.thread allows the use of OpenChatter
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
Use the thread viewer widget inside your form view by using the mail_thread
|
||||
widget on the message_ids field inherited from :class:`mail.thread`::
|
||||
|
||||
<record model="ir.ui.view" id="my_task_form_view">
|
||||
<field name="name">My Task</field>
|
||||
<field name="model">my.task</field>
|
||||
<field name="priority">1</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
[...]
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Send notifications
|
||||
+++++++++++++++++++
|
||||
|
||||
When sending a notification is required in your workflow or business logic,
|
||||
use :meth:`mail.thread.message_post`. It will automatically take care of
|
||||
subscriptions and notifications.
|
||||
|
||||
Here is a small example of sending a notification when the ``do_something``
|
||||
method is called::
|
||||
|
||||
def do_something(self, cr, uid, ids, context=None):
|
||||
self.do_something_send_note(cr, uid, ids, context=context)
|
||||
return res
|
||||
|
||||
def do_something_send_note(self, cr, uid, ids, context=None):
|
||||
self.message_post(
|
||||
cr, uid, ids, _('My subject'),
|
||||
_("has received a <b>notification</b> and is happy for it."),
|
||||
context=context)
|
||||
|
||||
Notifications guidelines
|
||||
+++++++++++++++++++++++++
|
||||
|
||||
- avoid unnecessary content, swamping users with irrelevant messages will lead
|
||||
to them ignoring all messages
|
||||
- use short sentences
|
||||
- do not include the document name, this is done by the thread widget
|
||||
- use a simple and clean style
|
||||
|
||||
- html tags are supported: use <b> or <em> mainly
|
||||
- put key word(s) in bold
|
||||
- avoid fancy styles that will break the OpenERP look and feel
|
||||
- create a separate method for sending your notification, use clear method
|
||||
names allowing quickly spotting notification code e.g. name notification
|
||||
methods by using the original method name postfixed by ``_send_note``
|
||||
(``do_something`` -> ``do_something_send_note``)
|
||||
|
||||
Subscription management
|
||||
++++++++++++++++++++++++
|
||||
|
||||
The default subscription behavior is the following:
|
||||
|
||||
* Subscriptions are set up by creating a :class:`mail.followers`` entry
|
||||
* If a user creates or updates a document, they automatically follow it. The
|
||||
corresponding :class:`mail.followers` entry is created
|
||||
* If a user explicitly cliks on the document's :guilabel:`Follow` button,
|
||||
they follow the document. The corresponding :class:`mail.followers` entry
|
||||
is created
|
||||
* If a user explicitly clicks on the document's :guilabel:`Unfollow` button,
|
||||
they stop following the document. The corresponding :class:`mail.followers`
|
||||
entry is deleted
|
||||
|
||||
You should not directly manipulate :class:`mail.followers` entry, if you need
|
||||
to override the default subscription behavior you should override the relevant
|
||||
:class:`mail.thread` methods.
|
||||
|
||||
.. TODO: wtf are the relevant mail.thread methds? message_get_subscribers
|
||||
has disappeared and nothing looks like a replacement
|
||||
@@ -1,10 +0,0 @@
|
||||
What is shown
|
||||
==============
|
||||
- for every module which are related to partner show apporopriate message in the partner view like opportunities, sale orders and invoices.
|
||||
|
||||
|
||||
How it is done
|
||||
===============
|
||||
- _inherit = 'mail.thread'
|
||||
|
||||
- Override def message_load_ids(self, cr, uid, ids, limit=100, offset=0, domain=[], ascent=False, root_ids=[], context=None) search by the partner
|
||||
@@ -1,28 +0,0 @@
|
||||
.. _mail_state:
|
||||
|
||||
message_unread
|
||||
==============
|
||||
|
||||
``message_unread`` is a boolean field that states whether the document
|
||||
has unread messages. In previous versions, some documents were going
|
||||
back to ``pending`` state when receiving an email through the mail
|
||||
gateway. Now the state related to messages differs from the state or
|
||||
stage of the document itself.
|
||||
|
||||
message_unread and need action mechanism
|
||||
++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
The ``mail`` module introduces a default behavior for the need_action
|
||||
mechanism [REF].
|
||||
|
||||
::
|
||||
|
||||
def get_needaction_user_ids(self, cr, uid, ids, context=None):
|
||||
""" Returns the user_ids that have to perform an action
|
||||
:return: dict { record_id: [user_ids], }
|
||||
"""
|
||||
result = super(ir_needaction_mixin, self).get_needaction_user_ids(cr, uid, ids, context=context)
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
if obj.message_unread == False and obj.user_id:
|
||||
result[obj.id].append(obj.user_id.id)
|
||||
return result
|
||||
@@ -1,70 +0,0 @@
|
||||
.. _mail_message_subtype:
|
||||
|
||||
OpenChatter Pi (3.1415): Message Subtype
|
||||
========================================
|
||||
|
||||
To overcome the problems of crowdy walls in system notification, We have added features of **Message Subtype** in mail.
|
||||
|
||||
mail.message.subtype
|
||||
++++++++++++++++++++
|
||||
``mail.message.subtype`` has following fields:
|
||||
|
||||
- ``Name``: fields.char(' Message Subtype ', size = 128,required = True,help = 'Subtype Of Message'),
|
||||
- ``model_ids``: fields.many2many('ir.model','mail_message_subtyp_message_rel','message_subtype_id', 'model_id', 'Model',help = "link some subtypes to several models, for projet/task"),
|
||||
- ``default``: fields.boolean('Default', help = "When subscribing to the document, users will receive by default messages related to this subtype unless they uncheck this subtype"),
|
||||
|
||||
mail.followers
|
||||
++++++++++++++
|
||||
|
||||
In ``mail.followers`` we have added additional many2many field subtype ids :
|
||||
|
||||
- ``subtype_ids``: fields.many2many('mail.message.subtype','mail_message_subtyp_rel','subscription_id', 'subtype_id', 'Subtype',help = "linking some subscription to several subtype for projet/task")
|
||||
|
||||
mail.message
|
||||
++++++++++++
|
||||
|
||||
In mail_message we have added additional field subtype_id which Indicates the Type of Message
|
||||
|
||||
- ``subtype_id``: fields.many2one('mail.message.subtype', 'Subtype')
|
||||
|
||||
mail.thread
|
||||
+++++++++++
|
||||
|
||||
- In **message_post** method add the *subtype_id* field as parameter and set as default subtype 'Other'.
|
||||
|
||||
def message_post(self, cr, uid, thread_id, body='', subject=False, msg_type='notification', parent_id=False, attachments=None, subtype='other', context=None, ``**kwargs``):
|
||||
|
||||
- In **message_subscribe** method add the *subtype_ids* field as parameter.In this method if subtype_ids is None, it fatch the default true subtypes in mail.message.subtypes otherwise pass selected subtypes.
|
||||
For update subtypes call **message_subscribe_udpate_subtypes** method
|
||||
|
||||
def message_subscribe(self, cr, uid, ids, partner_ids,subtype_ids = None, context=None):
|
||||
|
||||
- Add **message_subscribe_udpate_subtypes** method to update the subtype_ids in followers.
|
||||
|
||||
def message_subscribe_udpate_subtypes(self, cr, uid, ids, user_id, subtype_ids,context=None):
|
||||
followers_obj = self.pool.get('mail.followers')
|
||||
followers_ids = followers_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)])
|
||||
return followers_obj.write(cr, uid, followers_ids, {'subtype_ids': [(6, 0 , subtype_ids)]}, context = context)
|
||||
|
||||
For Each Addons:
|
||||
++++++++++++++++
|
||||
|
||||
- Add data of subtypes for each addons module.
|
||||
- Add subtype field as parameter in **message_post** Method for each addons module.
|
||||
|
||||
How It Works:
|
||||
+++++++++++++
|
||||
|
||||
- In addons module when we Follow a Perticular document It display under the followers button.
|
||||
- In sybtypes there are 3 default subtypes for each addons
|
||||
1) Email
|
||||
2) Comment
|
||||
3) Other
|
||||
- In document display a default subtypes(which are true) related a perticular model_ids wise.
|
||||
|
||||
Example:-
|
||||
If I have open crm.lead, It display only subtypes of crm.lead
|
||||
|
||||
- When we select subtype it update subtype_ids(which are checked) in mail.follower where match res_model & res_id of the current documents.
|
||||
- when message created update subtype_id of that message in mail.message.
|
||||
- In Feeds display only those notifications of documents which subtypes are selected
|
||||
@@ -1,53 +0,0 @@
|
||||
.. _mail_thread:
|
||||
|
||||
===========================
|
||||
mail.thread and OpenChatter
|
||||
===========================
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
Writing messages and notifications
|
||||
----------------------------------
|
||||
|
||||
``message_append``
|
||||
|
||||
Creates a new mail.message through message_create. The new message is attached
|
||||
to the current mail.thread, containing all the details passed as parameters.
|
||||
All attachments will be attached to the thread record as well as to the
|
||||
actual message.
|
||||
|
||||
This method calls message_create that will handle management of subscription
|
||||
and notifications, and effectively create the message.
|
||||
|
||||
If ``email_from`` is not set or ``type`` not set as 'email', a note message
|
||||
is created (comment or system notification), without the usual envelope
|
||||
attributes (sender, recipients, etc.).
|
||||
|
||||
mail.group
|
||||
++++++++++
|
||||
|
||||
A mail_group is a collection of users sharing messages in a discussion group. Group users are users that follow the mail group, using the subscription/follow mechanism of OpenSocial. A mail group has nothing in common wih res.users.group.
|
||||
Additional information on fields:
|
||||
|
||||
- ``member_ids``: user member of the groups are calculated with ``message_get_subscribers`` method from mail.thread
|
||||
- ``member_count``: calculated with member_ids
|
||||
- ``is_subscriber``: calculated with member_ids
|
||||
|
||||
res.users
|
||||
+++++++++
|
||||
|
||||
OpenChatter updates the res.users class:
|
||||
- it adds a preference about sending emails when receiving a notification
|
||||
- make a new user follow itself automatically
|
||||
- create a welcome message when creating a new user, to make his arrival in OpenERP more friendly
|
||||
|
||||
|
||||
Misc magic context keys
|
||||
+++++++++++++++++++++++
|
||||
|
||||
- mail_create_nosubscribe: when creating a new record that inherit from mail_thread,
|
||||
do not subscribe the creator to the document followers
|
||||
- mail_create_nolog: do not log creation message
|
||||
- mail_notify_noemail: do not send email notifications; partners to notify are
|
||||
notified, i.e. a mail_notification is created, but no email is actually send
|
||||
@@ -1,18 +0,0 @@
|
||||
Tracked fields
|
||||
---------------
|
||||
Tracked fields is generic logging system for the changes applied to some fields in open chatter.
|
||||
- In the definition of a fields. you have to simply add tracked=True.
|
||||
- When changing one of the fields having tracked=True on saved automatically write a log in openchatter for the changes in fields.
|
||||
|
||||
How it works:
|
||||
+++++++++++++
|
||||
- You have to add tracked=True in field defination as following.
|
||||
- ``'stage_id': fields.many2one('project.task.type', 'Stage',tracked=True),``
|
||||
- For developed this feature we override write method of mail_thread.
|
||||
- And make one mako template which shows old field and updated field.
|
||||
|
||||
Open chatter log:
|
||||
+++++++++++++++++
|
||||
- After changing field follower can show log of tracked field in open chatter as followed.
|
||||
- Updated Fields:
|
||||
- Stage: Analysis -> Specification
|
||||
@@ -45,7 +45,7 @@ def format_tz(env, dt, tz=False, format=False):
|
||||
format_time = langs.time_format or '%I-%M %p'
|
||||
|
||||
fdate = ts.strftime(format_date).decode('utf-8')
|
||||
ftime = ts.strftime(format_time)
|
||||
ftime = ts.strftime(format_time).decode('utf-8')
|
||||
return "%s %s%s" % (fdate, ftime, (' (%s)' % tz) if tz else '')
|
||||
|
||||
try:
|
||||
|
||||
@@ -5,6 +5,8 @@ import base64
|
||||
import datetime
|
||||
import dateutil
|
||||
import email
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import lxml
|
||||
import logging
|
||||
@@ -568,6 +570,13 @@ class MailThread(models.AbstractModel):
|
||||
because portal users have a different Inbox action than classic users. """
|
||||
return 'mail.mail_channel_action_client_chat'
|
||||
|
||||
@api.model
|
||||
def _generate_notification_token(self, base_link, params):
|
||||
secret = self.env['ir.config_parameter'].sudo().get_param('database.secret')
|
||||
token = '%s?%s' % (base_link, ' '.join('%s=%s' % (key, params[key]) for key in sorted(params.keys())))
|
||||
hm = hmac.new(str(secret), token, hashlib.sha1).hexdigest()
|
||||
return hm
|
||||
|
||||
@api.multi
|
||||
def _notification_link_helper(self, link_type, **kwargs):
|
||||
if kwargs.get('message_id'):
|
||||
@@ -576,24 +585,29 @@ class MailThread(models.AbstractModel):
|
||||
}
|
||||
else:
|
||||
base_params = {
|
||||
'model': kwargs.pop('model', self._name),
|
||||
'res_id': kwargs.pop('res_id', self.ids and self.ids[0] or False),
|
||||
'model': kwargs.get('model', self._name),
|
||||
'res_id': kwargs.get('res_id', self.ids and self.ids[0] or False),
|
||||
}
|
||||
|
||||
link = False
|
||||
if link_type in ['view', 'assign', 'follow', 'unfollow']:
|
||||
params = dict(base_params)
|
||||
link = '/mail/%s?%s' % (link_type, url_encode(params))
|
||||
elif link_type == 'workflow':
|
||||
params = dict(base_params, signal=kwargs['signal'])
|
||||
link = '/mail/workflow?%s' % url_encode(params)
|
||||
elif link_type == 'method':
|
||||
method = kwargs.pop('method')
|
||||
params = dict(base_params, method=method, params=json.dumps(kwargs))
|
||||
link = '/mail/method?%s' % url_encode(params)
|
||||
base_link = '/mail/%s' % link_type
|
||||
elif link_type == 'new':
|
||||
params = dict(base_params, action_id=kwargs.get('action_id'))
|
||||
link = '/mail/new?%s' % url_encode(params)
|
||||
params = dict(base_params, action_id=kwargs.get('action_id', ''))
|
||||
base_link = '/mail/new'
|
||||
elif link_type == 'controller':
|
||||
controller = kwargs.pop('controller')
|
||||
params = dict(base_params)
|
||||
params.pop('model')
|
||||
base_link = '%s' % controller
|
||||
else:
|
||||
return ''
|
||||
|
||||
if link_type not in ['view', 'new']:
|
||||
token = self._generate_notification_token(base_link, params)
|
||||
params['token'] = token
|
||||
|
||||
link = '%s?%s' % (base_link, url_encode(params))
|
||||
return link
|
||||
|
||||
@api.multi
|
||||
@@ -1030,7 +1044,7 @@ class MailThread(models.AbstractModel):
|
||||
tools.decode_message_header(message, 'Cc'),
|
||||
tools.decode_message_header(message, 'Resent-To'),
|
||||
tools.decode_message_header(message, 'Resent-Cc')])
|
||||
rcpt_tos_localparts = [e.split('@')[0] for e in tools.email_split(rcpt_tos)]
|
||||
rcpt_tos_localparts = [e.split('@')[0].lower() for e in tools.email_split(rcpt_tos)]
|
||||
|
||||
# 0. Verify whether this is a bounced email and use it to collect bounce data and update notifications for customers
|
||||
if bounce_alias and bounce_alias in email_to_localpart:
|
||||
|
||||
@@ -31,13 +31,13 @@ class PublisherWarrantyContract(AbstractModel):
|
||||
limit_date = datetime.datetime.now()
|
||||
limit_date = limit_date - datetime.timedelta(15)
|
||||
limit_date_str = limit_date.strftime(misc.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
nbr_users = Users.search_count([])
|
||||
nbr_active_users = Users.search_count([("login_date", ">=", limit_date_str)])
|
||||
nbr_users = Users.search_count([('active', '=', True)])
|
||||
nbr_active_users = Users.search_count([("login_date", ">=", limit_date_str), ('active', '=', True)])
|
||||
nbr_share_users = 0
|
||||
nbr_active_share_users = 0
|
||||
if "share" in Users._fields:
|
||||
nbr_share_users = Users.search_count([("share", "=", True)])
|
||||
nbr_active_share_users = Users.search_count([("share", "=", True), ("login_date", ">=", limit_date_str)])
|
||||
nbr_share_users = Users.search_count([("share", "=", True), ('active', '=', True)])
|
||||
nbr_active_share_users = Users.search_count([("share", "=", True), ("login_date", ">=", limit_date_str), ('active', '=', True)])
|
||||
user = self.env.user
|
||||
domain = [('application', '=', True), ('state', 'in', ['installed', 'to upgrade', 'to remove'])]
|
||||
apps = self.env['ir.module.module'].sudo().search_read(domain, ['name'])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Enhance team communication</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Enhance team communication</h2>
|
||||
<h3 class="oe_slogan">Group and private chat, integrated with all modules</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="mail_sc_00.png" height="350">
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Engage conversation with colleagues</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Engage conversation with colleagues</h2>
|
||||
<h3 class="oe_slogan">A powerful communication tool</h3>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_row_img oe_centered">
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Chat in live from anywhere</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Chat in live from anywhere</h2>
|
||||
<h3 class="oe_slogan">Keep conversations open in other modules</h3>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32">
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Communicate with the right target</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Communicate with the right target</h2>
|
||||
<h3 class="oe_slogan">Create channels and private groups</h3>
|
||||
<p class="oe_mt32">
|
||||
Start discussion with the right people with channels and allow any employee to join in to the conversation.Create private channels to invite only a selected group of people and prevent unrelated employees to pollute the conversations.
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Personalize notifications</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Personalize notifications</h2>
|
||||
<h3 class="oe_slogan">Send notifications only when you want to</h3>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32 text-justify">
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Integrated with all modules</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Integrated with all modules</h2>
|
||||
<h3 class="oe_slogan">Keep track of all actions within one window</h3>
|
||||
<p class="oe_mt32">
|
||||
Follow any discussion from any module to automatically see updates and discussions on those tasks in your news feed. Create public and private groups to invite participants, personalize the notifications system and add those groups to specific tasks to keep track of the evolution of all projects.
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
<t t-if="message.origin_id && (message.origin_id !== options.channel_id)">
|
||||
(from <a t-att-data-oe-id="message.origin_id" href="#">#<t t-esc="message.origin_name"/></a>)
|
||||
</t>
|
||||
<span t-if="options.display_email_icon && message.customer_email_data.length" class="o_thread_tooltip_container">
|
||||
<span t-if="options.display_email_icon && message.customer_email_data && message.customer_email_data.length" class="o_thread_tooltip_container">
|
||||
<i t-att-class="'o_thread_tooltip o_thread_message_email o_thread_message_email_' + message.customer_email_status + ' fa fa-envelope-o'"/>
|
||||
<span class="o_thread_tooltip_content">
|
||||
<t t-foreach="message.customer_email_data" t-as="customer">
|
||||
|
||||
@@ -10,3 +10,4 @@ import test_message_track
|
||||
import test_mail_template
|
||||
import test_invite
|
||||
import test_ir_actions
|
||||
import test_update_notification
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestUpdateNotification(TransactionCase):
|
||||
def test_user_count(self):
|
||||
ping_msg = self.env['publisher_warranty.contract'].with_context(active_test=False)._get_message()
|
||||
user_count = self.env['res.users'].search_count([('active', '=', True)])
|
||||
self.assertEqual(ping_msg.get('nbr_users'), user_count, 'Update Notification: Users count is badly computed in ping message')
|
||||
share_user_count = self.env['res.users'].search_count([('active', '=', True), ('share', '=', True)])
|
||||
self.assertEqual(ping_msg.get('nbr_share_users'), share_user_count, 'Update Notification: Portal Users count is badly computed in ping message')
|
||||
@@ -144,7 +144,7 @@ class MarketingCampaign(models.Model):
|
||||
similar_res_ids = self.env[self.object_id.model].search([(unique_field.name, '=', unique_value)])
|
||||
if similar_res_ids:
|
||||
duplicate_workitem_domain = [
|
||||
('res_id', 'in', similar_res_ids),
|
||||
('res_id', 'in', similar_res_ids.ids),
|
||||
('campaign_id', '=', self.id)
|
||||
]
|
||||
return self.env['marketing.campaign.workitem'].search(duplicate_workitem_domain)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Better manage leads with marketing automation</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Better manage leads with marketing automation</h2>
|
||||
<h3 class="oe_slogan">Nurture all of your prospects with a personal and targeted message</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="campaign.png" height="300">
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Generate new opportunities effortlessly</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Generate new opportunities effortlessly</h2>
|
||||
<h3 class="oe_slogan oe_mb32">Streamline your marketing & take care of your leads 24/7</h3>
|
||||
<div class="oe_span4">
|
||||
<div class="text-center">
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Deliver a message that resonates with your prospects</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Deliver a message that resonates with your prospects</h2>
|
||||
<h3 class="oe_slogan">Create advanced and personal emails that don't look automated</h3>
|
||||
<div class="oe_span6 text-justify">
|
||||
<h4 class="oe_mt32 oe_mb16">
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Shorten your sales cycle</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Shorten your sales cycle</h2>
|
||||
<h3 class="oe_slogan">Manage your prospects better by combining lead scoring and lead automation</h3>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="lead_form.png">
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#A24689;">Follow the ROI of all your marketing activities</h2>
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Follow the ROI of all your marketing activities</h2>
|
||||
<h3 class="oe_slogan">Make smarter decisions thanks to our powerful analytics reports</h3>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32 text-justify">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user