[IMP] split crm_case_section + create alias from lead

This commit is contained in:
Ravi Gadhia 2014-05-02 14:27:50 +05:30
commit 78204703b0
160 changed files with 688 additions and 596 deletions

View File

@ -1944,15 +1944,17 @@ class account_tax(osv.osv):
return super(account_tax, self).write(cr, uid, ids, vals, context=context)
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
if context is None:
context = {}
journal_pool = self.pool.get('account.journal')
if context and context.has_key('type'):
if context.get('type'):
if context.get('type') in ('out_invoice','out_refund'):
args += [('type_tax_use','in',['sale','all'])]
elif context.get('type') in ('in_invoice','in_refund'):
args += [('type_tax_use','in',['purchase','all'])]
if context and context.has_key('journal_id'):
if context.get('journal_id'):
journal = journal_pool.browse(cr, uid, context.get('journal_id'))
if journal.type in ('sale', 'purchase'):
args += [('type_tax_use','in',[journal.type,'all'])]

View File

@ -409,9 +409,7 @@ class account_invoice(osv.osv):
'''
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
self.write(cr, uid, ids, {'sent': True}, context=context)
context2 = context.copy()
context2['active_ids'] = ids
return self.pool['report'].get_action(cr, uid, [], 'account.report_invoice', context=context2)
return self.pool['report'].get_action(cr, uid, [], 'account.report_invoice', context=context)
def action_invoice_sent(self, cr, uid, ids, context=None):
'''

View File

@ -457,7 +457,7 @@
<filter name="invoices" string="Invoices" domain="[('state','not in',['draft','cancel'])]" help="Proforma/Open/Paid Invoices"/>
<filter name="unpaid" string="Unpaid" domain="[('state','=','open')]" help="Unpaid Invoices"/>
<separator/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="partner_id" operator="child_of"/>
<field name="user_id" string="Salesperson"/>
<field name="period_id" string="Period"/>
<separator/>

View File

@ -1034,10 +1034,14 @@ class account_move_line(osv.osv):
part_rec_ids = [rec['reconcile_partial_id'][0] for rec in part_recs]
unlink_ids += rec_ids
unlink_ids += part_rec_ids
all_moves = obj_move_line.search(cr, uid, ['|',('reconcile_id', 'in', unlink_ids),('reconcile_partial_id', 'in', unlink_ids)])
all_moves = list(set(all_moves) - set(move_ids))
if unlink_ids:
if opening_reconciliation:
obj_move_rec.write(cr, uid, unlink_ids, {'opening_reconciliation': False})
obj_move_rec.unlink(cr, uid, unlink_ids)
if all_moves:
obj_move_line.reconcile_partial(cr, uid, all_moves, 'auto',context=context)
return True
def unlink(self, cr, uid, ids, context=None, check=True):

View File

@ -65,7 +65,8 @@
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button type="action" string="Invoices"
name="%(account.action_invoice_tree)d"
name="%(account.action_invoice_tree1)d"
attrs="{'invisible': [('customer', '=', False)]}"
context="{'search_default_partner_id': active_id,'default_partner_id': active_id}" groups="account.group_account_invoice"/>
<button type="action" string="Journal Items" name="%(account.action_account_moves_all_tree)d" groups="account.group_account_user"/>
<button type="action" string="Contracts" name="%(account.action_open_partner_analytic_accounts)d"

View File

@ -31,7 +31,7 @@
<search string="Analytic Account">
<field name="name" filter_domain="['|', ('name','ilike',self), ('code','ilike',self)]" string="Analytic Account"/>
<field name="date"/>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
<field name="partner_id" operator="child_of"/>
<field name="manager_id"/>
<field name="parent_id"/>
<field name="user_id"/>

View File

@ -22,6 +22,7 @@ from dateutil.relativedelta import relativedelta
import datetime
import logging
import time
import traceback
from openerp.osv import osv, fields
from openerp.osv.orm import intersect, except_orm
@ -72,9 +73,7 @@ class account_analytic_invoice_line(osv.osv):
result = {}
res = self.pool.get('product.product').browse(cr, uid, product, context=context)
result.update({'name':res.partner_ref or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': res.list_price or 0.0})
if res.description:
result['name'] += '\n'+res.description
result.update({'name': name or res.description or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': price_unit or res.list_price or 0.0})
res_final = {'value':result}
if result['uom_id'] != res.uom_id.id:
@ -721,23 +720,40 @@ class account_analytic_account(osv.osv):
inv_obj.button_compute(cr, uid, [invoice_id], context=context)
return invoice_id
def recurring_create_invoice(self, cr, uid, automatic=False, context=None):
def recurring_create_invoice(self, cr, uid, ids, context=None):
return self._recurring_create_invoice(cr, uid, ids, context=context)
def _cron_recurring_create_invoice(self, cr, uid, context=None):
return self._recurring_create_invoice(cr, uid, [], automatic=True, context=context)
def _recurring_create_invoice(self, cr, uid, ids, automatic=False, context=None):
context = context or {}
current_date = time.strftime('%Y-%m-%d')
contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True)])
if ids:
contract_ids = ids
else:
contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True), ('type', '=', 'contract')])
for contract in self.browse(cr, uid, contract_ids, context=context):
invoice_id = self._prepare_invoice(cr, uid, contract, context=context)
try:
invoice_id = self._prepare_invoice(cr, uid, contract, context=context)
next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d")
interval = contract.recurring_interval
if contract.recurring_rule_type == 'daily':
new_date = next_date+relativedelta(days=+interval)
elif contract.recurring_rule_type == 'weekly':
new_date = next_date+relativedelta(weeks=+interval)
else:
new_date = next_date+relativedelta(months=+interval)
self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context)
next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d")
interval = contract.recurring_interval
if contract.recurring_rule_type == 'daily':
new_date = next_date+relativedelta(days=+interval)
elif contract.recurring_rule_type == 'weekly':
new_date = next_date+relativedelta(weeks=+interval)
else:
new_date = next_date+relativedelta(months=+interval)
self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context)
if automatic:
cr.commit()
except Exception:
if automatic:
cr.rollback()
_logger.error(traceback.format_exc())
else:
raise
return True
class account_analytic_account_summary_user(osv.osv):

View File

@ -93,7 +93,7 @@ OpenERP Automatic Email
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="model" eval="'account.analytic.account'"/>
<field name="function" eval="'recurring_create_invoice'"/>
<field name="function" eval="'_cron_recurring_create_invoice'"/>
<field name="args" eval="'()'"/>
</record>

View File

@ -151,8 +151,10 @@
</group>
<separator string="Recurring Invoices" attrs="{'invisible': [('recurring_invoices','!=',True)]}"/>
<div>
<field name="recurring_invoices" on_change="onchange_recurring_invoices(recurring_invoices, date_start)" class="oe_inline"/>
<label for="recurring_invoices" />
<div attrs="{'invisible': [('type','!=', 'contract'), ('recurring_invoices', '=', False)]}">
<field name="recurring_invoices" on_change="onchange_recurring_invoices(recurring_invoices, date_start)" class="oe_inline" />
<label for="recurring_invoices" />
</div>
<button class="oe_link" name="recurring_create_invoice" attrs="{'invisible': [('recurring_invoices','!=',True)]}" string="⇒ create invoices" type="object" groups="base.group_no_one"/>
</div>
<group attrs="{'invisible': [('recurring_invoices','!=',True)]}">

View File

@ -20,6 +20,7 @@
!record {model: account.analytic.account, id: contract_main}:
partner_id: base.main_partner
template_id: account_analytic_analysis.contract_template
type: contract
-
I check that the contract inherited from data of the template
-
@ -32,7 +33,7 @@
I generate all invoices from contracts having recurring invoicing
-
!python {model: account.analytic.account}: |
self.recurring_create_invoice(cr, uid)
self.recurring_create_invoice(cr, uid, [])
-
I test the generated invoice
-

View File

@ -454,15 +454,10 @@ class account_bank_statement(osv.osv):
_inherit = "account.bank.statement"
_name = "account.bank.statement"
def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, st_line_number, context=None):
account_move_line_pool = self.pool.get('account.move.line')
account_bank_statement_line_pool = self.pool.get('account.bank.statement.line')
st_line = account_bank_statement_line_pool.browse(cr, uid, st_line_id, context=context)
result = super(account_bank_statement,self).create_move_from_st_line(cr, uid, st_line_id, company_currency_id, st_line_number, context=context)
move = st_line.move_ids and st_line.move_ids[0] or False
if move:
for line in move.line_id:
account_move_line_pool.write(cr, uid, [line.id], {'analytics_id':st_line.analytics_id.id}, context=context)
def _prepare_bank_move_line(self, cr, uid, st_line, move_id, amount, company_currency_id, context=None):
result = super(account_bank_statement,self)._prepare_bank_move_line(cr, uid, st_line,
move_id, amount, company_currency_id, context=context)
result['analytics_id'] = st_line.analytics_id.id
return result
def button_confirm_bank(self, cr, uid, ids, context=None):

View File

@ -123,6 +123,8 @@ class account_invoice_line(osv.osv):
if a == line['account_id'] and i_line.product_id.id == line['product_id']:
uom = i_line.product_id.uos_id or i_line.product_id.uom_id
valuation_price_unit = self.pool.get('product.uom')._compute_price(cr, uid, uom.id, i_line.product_id.standard_price, i_line.uos_id.id)
if inv.currency_id.id != company_currency:
standard_price = self.pool.get('res.currency').compute(cr, uid, company_currency, inv.currency_id.id, standard_price, context={'date': inv.date_invoice})
if i_line.product_id.cost_method != 'standard' and i_line.purchase_line_id:
#for average/fifo/lifo costing method, fetch real cost price from incomming moves
stock_move_obj = self.pool.get('stock.move')

View File

@ -783,6 +783,7 @@ class account_voucher(osv.osv):
total_credit += line.credit and line.amount_currency or 0.0
total_debit += line.debit and line.amount_currency or 0.0
remaining_amount = price
#voucher line creation
for line in account_move_lines:
@ -803,13 +804,13 @@ class account_voucher(osv.osv):
'move_line_id':line.id,
'account_id':line.account_id.id,
'amount_original': amount_original,
'amount': (line.id in move_lines_found) and min(abs(price), amount_unreconciled) or 0.0,
'amount': (line.id in move_lines_found) and min(abs(remaining_amount), amount_unreconciled) or 0.0,
'date_original':line.date,
'date_due':line.date_maturity,
'amount_unreconciled': amount_unreconciled,
'currency_id': line_currency_id,
}
price -= rs['amount']
remaining_amount -= rs['amount']
#in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
#on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
if not move_lines_found:
@ -937,19 +938,17 @@ class account_voucher(osv.osv):
def cancel_voucher(self, cr, uid, ids, context=None):
reconcile_pool = self.pool.get('account.move.reconcile')
move_pool = self.pool.get('account.move')
move_line_pool = self.pool.get('account.move.line')
for voucher in self.browse(cr, uid, ids, context=context):
# refresh to make sure you don't unlink an already removed move
voucher.refresh()
recs = []
for line in voucher.move_ids:
if line.reconcile_id:
recs += [line.reconcile_id.id]
if line.reconcile_partial_id:
recs += [line.reconcile_partial_id.id]
reconcile_pool.unlink(cr, uid, recs)
move_lines = [move_line.id for move_line in line.reconcile_id.line_id]
move_lines.remove(line.id)
reconcile_pool.unlink(cr, uid, [line.reconcile_id.id])
if len(move_lines) >= 2:
move_line_pool.reconcile_partial(cr, uid, move_lines, 'auto',context=context)
if voucher.move_id:
move_pool.button_cancel(cr, uid, [voucher.move_id.id])
move_pool.unlink(cr, uid, [voucher.move_id.id])

View File

@ -44,6 +44,8 @@
!python {model: account.voucher}: |
vals = {}
journal_id = self.default_get(cr, uid, ['journal_id']).get('journal_id',None)
voucher = self.recompute_voucher_lines(cr, uid, [], ref("base.res_partner_19"), journal_id, 450.0, ref('base.EUR'), 'receipt', False)
assert (voucher['value'].get('writeoff_amount') == 0.0), "Writeoff amount calculated by recompute_voucher_lines() is not 0.0"
res = self.onchange_partner_id(cr, uid, [], ref("base.res_partner_19"), journal_id, 0.0, 1, ttype='receipt', date=False)
vals = {
'account_id': ref('account.cash'),
@ -64,6 +66,7 @@
vals['line_cr_ids'] = [(0,0,i) for i in res['value']['line_cr_ids']]
id = self.create(cr, uid, vals)
voucher_id = self.browse(cr, uid, id)
assert (voucher_id.writeoff_amount == 0.0), "Writeoff amount is not 0.0"
assert (voucher_id.state=='draft'), "Voucher is not in draft state"
self.signal_proforma_voucher(cr, uid, [voucher_id.id])

View File

@ -5,8 +5,8 @@
<data>
<template id="auth_signup.login" inherit_id="web.login" name="Sign up - Reset Password">
<xpath expr="//button[@type='submit']" position="before">
<a t-if="signup_enabled" t-attf-href="/web/signup?redirect=/web%3f{{ quote_plus(keep_query()) }}" class="btn btn-link pull-right">Sign up</a>
<a t-if="reset_password_enabled" t-attf-href="/web/reset_password?redirect=/web/login%3f{{ quote_plus(keep_query()) }}" class="btn btn-link pull-right">Reset Password</a>
<a t-if="signup_enabled" t-attf-href="/web/signup?{{ keep_query() }}" class="btn btn-link pull-right">Sign up</a>
<a t-if="reset_password_enabled" t-attf-href="/web/reset_password?{{ keep_query() }}" class="btn btn-link pull-right">Reset Password</a>
</xpath>
</template>
@ -22,12 +22,13 @@
<div class="form-group field-name">
<label for="name" class="control-label">Your Name</label>
<input type="text" name="name" t-att-value="name" id="name" class="form-control" placeholder="e.g. John Doe"
required="required" t-att-readonly="'readonly' if only_passwords else None"/>
required="required" t-att-readonly="'readonly' if only_passwords else None"
t-att-autofocus="'autofocus' if login and not only_passwords else None" />
</div>
<div class="form-group field-password">
<label for="password" class="control-label">Password</label>
<input type="password" name="password" autofocus="autofocus" id="password" class="form-control"
<input type="password" name="password" id="password" class="form-control"
required="required" t-att-autofocus="'autofocus' if only_passwords else None"/>
</div>
@ -39,7 +40,7 @@
<template id="auth_signup.signup" name="Sign up login">
<t t-call="web.login_layout">
<form class="oe_signup_form" role="form" t-attf-action="/web/signup{{ '?debug' if debug else '' }}" method="post" t-if="not message">
<form class="oe_signup_form" role="form" method="post" t-if="not message">
<t t-call="auth_signup.fields"/>
@ -49,7 +50,7 @@
<input type="hidden" name="redirect" t-att-value="redirect"/>
<input type="hidden" name="token" t-att-value="token"/>
<div class="clearfix oe_login_buttons">
<a href="/web/login" class="btn btn-link pull-right">Back to Login</a>
<a t-attf-href="/web/login?{{ keep_query() }}" class="btn btn-link pull-right">Back to Login</a>
<button type="submit" class="btn btn-primary pull-left">Sign up</button>
</div>
@ -66,7 +67,7 @@
<a href="/web/login" class="btn btn-link pull-right">Back to Login</a>
</div>
<form class="oe_reset_password_form" role="form" t-attf-action="/web/reset_password{{ '?debug' if debug else '' }}" method="post" t-if="not message">
<form class="oe_reset_password_form" role="form" method="post" t-if="not message">
<t t-if="token">
<t t-call="auth_signup.fields">
@ -88,7 +89,7 @@
<input type="hidden" name="redirect" t-att-value="redirect"/>
<input type="hidden" name="token" t-att-value="token"/>
<div class="clearfix oe_login_buttons">
<a href="/web/login" class="btn btn-link pull-right">Back to Login</a>
<a t-attf-href="/web/login?{{ keep_query() }}" class="btn btn-link pull-right">Back to Login</a>
<button type="submit" class="btn btn-primary pull-left">Reset password</button>
</div>

View File

@ -8,7 +8,7 @@
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook[last()]" position="inside">
<page string="Geo Localization" name="geo_localization" groups="base.group_no_one">
<page string="Geo Localization" name="geo_localization">
<group colspan="2" col="2">
<separator string="Geo Localization" colspan="2"/>
<button

View File

@ -91,10 +91,10 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
var $dialog = new instance.web.Dialog(this, {
title: _t("Edit Layout"),
}, QWeb.render('DashBoard.layouts', qdict)).open();
$dialog.find('li').click(function() {
$dialog.$el.find('li').click(function() {
var layout = $(this).attr('data-layout');
$dialog.modal('hide');
self.do_change_layout(layout);
$dialog.$dialog_box.modal('hide');
});
},
do_change_layout: function(new_layout) {

View File

@ -994,7 +994,6 @@ class calendar_event(osv.Model):
sort_fields[ord] = '%s-%s' % (browse_event[ord], r_date.strftime("%Y%m%d%H%M%S"))
else:
sort_fields[ord] = browse_event[ord]
'If we sort on FK, we obtain a browse_record, so we need to sort on name_get'
if type(browse_event[ord]) is openerp.osv.orm.browse_record:
name_get = browse_event[ord].name_get()
if len(name_get) and len(name_get[0]) >= 2:
@ -1397,6 +1396,14 @@ class calendar_event(osv.Model):
'flags': {'form': {'action_buttons': True, 'options': {'mode': 'edit'}}}
}
def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
for arg in args:
if arg[0] == 'id':
for n, calendar_id in enumerate(arg[2]):
if isinstance(calendar_id, str):
arg[2][n] = calendar_id.split('-')[0]
return super(calendar_event, self)._name_search(cr, user, name=name, args=args, operator=operator, context=context, limit=limit, name_get_uid=name_get_uid)
def write(self, cr, uid, ids, values, context=None):
def _only_changes_to_apply_on_real_ids(field_names):
''' return True if changes are only to be made on the real ids'''

View File

@ -39,8 +39,9 @@ class meeting_invitation(http.Controller):
if attendee:
attendee_data['current_attendee'] = attendee[0]
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in webmain.manifest_list('js', db=db))
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in webmain.manifest_list('css', db=db))
css = '<link rel="stylesheet" href="/web/css/web.assets_backend"/>'
js = '<script type="text/javascript" src="/web/js/web.assets_backend"></script>'
return webmain.html_template % {
'js': js,

View File

@ -22,6 +22,7 @@
import crm
import crm_segmentation
import crm_lead
import crm_case_section
import calendar_event
import crm_phonecall
import report

View File

@ -88,74 +88,6 @@ class crm_case_stage(osv.osv):
}
class crm_case_section(osv.Model):
_inherit = 'crm.case.section'
_inherits = {'mail.alias': 'alias_id'}
def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None):
""" Get opportunities-related data for salesteam kanban view
monthly_open_leads: number of open lead during the last months
monthly_planned_revenue: planned revenu of opportunities during the last months
"""
obj = self.pool.get('crm.lead')
res = dict.fromkeys(ids, False)
month_begin = date.today().replace(day=1)
date_begin = month_begin - relativedelta.relativedelta(months=self._period_number - 1)
date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1])
lead_pre_domain = [('create_date', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
('create_date', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
('type', '=', 'lead')]
opp_pre_domain = [('date_deadline', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
('date_deadline', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
('type', '=', 'opportunity')]
for id in ids:
res[id] = dict()
lead_domain = lead_pre_domain + [('section_id', '=', id)]
opp_domain = opp_pre_domain + [('section_id', '=', id)]
res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context)
res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'date_deadline'], 'planned_revenue', 'date_deadline', context=context)
return res
_columns = {
'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"),
'stage_ids': fields.many2many('crm.case.stage', 'section_stage_rel', 'section_id', 'stage_id', 'Stages'),
'use_leads': fields.boolean('Leads',
help="The first contact you get with a potential customer is a lead you qualify before converting it into a real business opportunity. Check this box to manage leads in this sales team."),
'monthly_open_leads': fields.function(_get_opportunities_data,
type="string", readonly=True, multi='_get_opportunities_data',
string='Open Leads per Month'),
'monthly_planned_revenue': fields.function(_get_opportunities_data,
type="string", readonly=True, multi='_get_opportunities_data',
string='Planned Revenue per Month'),
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically ""create new leads assigned to the team."),
}
def _get_stage_common(self, cr, uid, context):
ids = self.pool.get('crm.case.stage').search(cr, uid, [('case_default', '=', 1)], context=context)
return ids
_defaults = {
'stage_ids': _get_stage_common,
'use_leads': True,
}
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
create_context = dict(context, alias_model_name='crm.lead', alias_parent_model_name=self._name)
section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context)
section = self.browse(cr, uid, section_id, context=context)
self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context)
return section_id
def unlink(self, cr, uid, ids, context=None):
# Cascade-delete mail aliases as well, as they should not exist without the sales team.
mail_alias = self.pool.get('mail.alias')
alias_ids = [team.alias_id.id for team in self.browse(cr, uid, ids, context=context) if team.alias_id]
res = super(crm_case_section, self).unlink(cr, uid, ids, context=context)
mail_alias.unlink(cr, uid, alias_ids, context=context)
return res
class crm_case_categ(osv.osv):
""" Category of Case """
_name = "crm.case.categ"

View File

@ -0,0 +1,81 @@
import calendar
from datetime import date
from dateutil import relativedelta
from openerp import tools
from openerp.osv import fields, osv
class crm_case_section(osv.Model):
_inherit = 'crm.case.section'
_inherits = {'mail.alias': 'alias_id'}
def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None):
""" Get opportunities-related data for salesteam kanban view
monthly_open_leads: number of open lead during the last months
monthly_planned_revenue: planned revenu of opportunities during the last months
"""
obj = self.pool.get('crm.lead')
res = dict.fromkeys(ids, False)
month_begin = date.today().replace(day=1)
date_begin = month_begin - relativedelta.relativedelta(months=self._period_number - 1)
date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1])
lead_pre_domain = [('create_date', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
('create_date', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
('type', '=', 'lead')]
opp_pre_domain = [('date_deadline', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
('date_deadline', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
('type', '=', 'opportunity')]
for id in ids:
res[id] = dict()
lead_domain = lead_pre_domain + [('section_id', '=', id)]
opp_domain = opp_pre_domain + [('section_id', '=', id)]
res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context)
res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'date_deadline'], 'planned_revenue', 'date_deadline', context=context)
return res
_columns = {
'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"),
'stage_ids': fields.many2many('crm.case.stage', 'section_stage_rel', 'section_id', 'stage_id', 'Stages'),
'use_leads': fields.boolean('Leads',
help="The first contact you get with a potential customer is a lead you qualify before converting it into a real business opportunity. Check this box to manage leads in this sales team."),
'monthly_open_leads': fields.function(_get_opportunities_data,
type="string", readonly=True, multi='_get_opportunities_data',
string='Open Leads per Month'),
'monthly_planned_revenue': fields.function(_get_opportunities_data,
type="string", readonly=True, multi='_get_opportunities_data',
string='Planned Revenue per Month'),
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically ""create new leads assigned to the team."),
}
def _auto_init(self, cr, context=None):
"""Installation hook to create aliases for all lead and avoid constraint errors."""
return self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(crm_case_section, self)._auto_init,
'crm.lead', self._columns['alias_id'], 'name', alias_prefix='Lead+', alias_defaults={}, context=context)
def _get_stage_common(self, cr, uid, context):
ids = self.pool.get('crm.case.stage').search(cr, uid, [('case_default', '=', 1)], context=context)
return ids
_defaults = {
'stage_ids': _get_stage_common,
'use_leads': True,
}
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
create_context = dict(context, alias_model_name='crm.lead', alias_parent_model_name=self._name)
section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context)
section = self.browse(cr, uid, section_id, context=context)
self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context)
return section_id
def unlink(self, cr, uid, ids, context=None):
# Cascade-delete mail aliases as well, as they should not exist without the sales team.
mail_alias = self.pool.get('mail.alias')
alias_ids = [team.alias_id.id for team in self.browse(cr, uid, ids, context=context) if team.alias_id]
res = super(crm_case_section, self).unlink(cr, uid, ids, context=context)
mail_alias.unlink(cr, uid, alias_ids, context=context)
return res

View File

@ -982,7 +982,7 @@ class crm_lead(format_address, osv.osv):
if obj.type == 'opportunity':
model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor')
else:
view_id = super(crm_lead, self).get_formview_id(cr, uid, id, model=model, context=context)
view_id = super(crm_lead, self).get_formview_id(cr, uid, id, model='crm.lead', context=context)
return view_id
def message_get_suggested_recipients(self, cr, uid, ids, context=None):

View File

@ -327,7 +327,7 @@
<field name="categ_ids" string="Tag" filter_domain="[('categ_ids', 'ilike', self)]"/>
<field name="section_id" context="{'invisible_section': False}" groups="base.group_multi_salesteams"/>
<field name="user_id"/>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
<field name="partner_id" operator="child_of"/>
<field name="create_date"/>
<field name="country_id" context="{'invisible_country': False}"/>
<separator/>
@ -547,7 +547,7 @@
<field name="categ_ids" string="Tag" filter_domain="[('categ_ids', 'ilike', self)]"/>
<field name="section_id" context="{'invisible_section': False}" groups="base.group_multi_salesteams"/>
<field name="user_id"/>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
<field name="partner_id" operator="child_of"/>
<field name="stage_id" domain="[]"/>
<field name="probability"/>
<separator/>

View File

@ -176,7 +176,7 @@
<separator/>
<filter string="Phone Calls Assigned to Me or My Team(s)" icon="terp-personal+" domain="['|', ('section_id.user_id','=',uid), ('user_id', '=', uid)]"
help="Phone Calls Assigned to the current user or with a team having the current user as team leader"/>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
<field name="partner_id" operator="child_of"/>
<field name="user_id"/>
<field name="opportunity_id"/>
<field name="section_id" string="Sales Team"

View File

@ -10,7 +10,7 @@
<field name="arch" type="xml">
<graph string="Leads Analysis" type="pivot" stacked="True">
<field name="user_id" type="row"/>
<field name="create_date" interval="week" type="col"/>
<field name="create_date" interval="year" type="col"/>
</graph>
</field>
</record>
@ -32,7 +32,7 @@
<field name="arch" type="xml">
<graph string="Leads Analysis" type="pivot" stacked="True">
<field name="date_deadline" type="row"/>
<field name="user_id" type="col"/>
<field name="stage_id" type="col"/>
<field name="planned_revenue" type="measure"/>
</graph>
</field>
@ -45,6 +45,9 @@
<field name="model">crm.lead.report</field>
<field name="arch" type="xml">
<search string="Leads Analysis">
<filter name="lead" string="Lead" domain="[('type','=', 'lead')]" help="Show only lead"/>
<filter name="opportunity" string="Opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
<separator/>
<filter string="New" name="new"
domain="[('probability', '=', 0), ('stage_id.sequence', '=', 1)]"/>
<filter string="Won" name="won"
@ -77,43 +80,37 @@
<field name="date_closed"/>
</group>
<group expand="1" string="Group By...">
<filter string="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}" />
<filter string="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}" />
<filter string="Partner" icon="terp-partner" context="{'group_by':'partner_id'}" />
<filter string="Country" icon="terp-go-home" context="{'group_by':'country_id'}" />
<filter string="Company" icon="terp-go-home" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Priority" icon="terp-rating-rated" domain="[]" context="{'group_by':'priority'}" />
<filter string="Campaign" icon="terp-gtk-jump-to-rtl"
domain="[]" context="{'group_by':'type_id'}" />
<filter string="Channel" icon="terp-call-start"
domain="[]" context="{'group_by':'channel_id'}" />
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}" />
<filter string="Sales Team" domain="[]" context="{'group_by':'section_id'}" />
<filter string="Partner" context="{'group_by':'partner_id'}" />
<filter string="Country" context="{'group_by':'country_id'}" />
<filter string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Type" domain="[]" context="{'group_by':'type'}"/>
<filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Priority" domain="[]" context="{'group_by':'priority'}" />
<filter string="Campaign" domain="[]" context="{'group_by':'type_id'}" />
<filter string="Channel" domain="[]" context="{'group_by':'channel_id'}" />
<separator orientation="vertical" />
<filter string="Creation date (day)" icon="terp-go-year"
domain="[]" context="{'group_by':'create_date:day'}"/>
<filter string="Creation date (week)" icon="terp-go-year"
domain="[]" context="{'group_by':'create_date:week'}"/>
<filter string="Creation date (month)" icon="terp-go-year"
domain="[]" context="{'group_by':'create_date:month'}" name="month"/>
<filter string="Creation date (year)" icon="terp-go-year"
domain="[]" context="{'group_by':'create_date:year'}"/>
<filter string="Creation date (day)" domain="[]" context="{'group_by':'create_date:day'}"/>
<filter string="Creation date (week)" domain="[]" context="{'group_by':'create_date:week'}"/>
<filter string="Creation date (month)" domain="[]" context="{'group_by':'create_date:month'}" name="month"/>
<filter string="Creation date (year)" domain="[]" context="{'group_by':'create_date:year'}"/>
<separator orientation="vertical" />
<filter string="Exp. Closing" icon="terp-go-month"
domain="[]" context="{'group_by':'date_deadline'}"/>
<filter string="Exp. Closing" domain="[]" context="{'group_by':'date_deadline'}"/>
<filter string="Last Stage Update" context="{'group_by':'date_last_stage_update'}" />
</group>
</search>
</field>
</record>
<!-- Leads by user and section Action -->
<!-- Leads by user and section Action -->
<record id="action_report_crm_lead" model="ir.actions.act_window">
<field name="name">Leads Analysis</field>
<field name="res_model">crm.lead.report</field>
<field name="view_type">form</field>
<field name="view_mode">graph</field>
<field name="domain">[('type','=', 'lead')]</field>
<field name="context">{'search_default_lead': 1}</field>
<field name="domain">[]</field>
<field name="help">Leads Analysis allows you to check different CRM related information like the treatment delays or number of leads per state. You can sort out your leads analysis by different groups to get accurate grained analysis.</field>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_lead_graph">
@ -122,27 +119,25 @@
<field name="view_id" ref="view_report_crm_lead_graph"/>
<field name="act_window_id" ref="action_report_crm_lead"/>
</record>
<menuitem name="Leads Analysis" id="menu_report_crm_leads_tree"
groups="base.group_sale_manager"
parent="base.next_id_64" action="action_report_crm_lead" sequence="1"/>
<record id="action_report_crm_opportunity" model="ir.actions.act_window">
<field name="name">Opportunities Analysis</field>
<field name="res_model">crm.lead.report</field>
<field name="view_type">form</field>
<field name="view_mode">graph</field>
<field name="domain">[('type','=', 'opportunity')]</field>
<field name="context">{'search_default_opportunity': 1}</field>
<field name="domain">[]</field>
<field name="help">Opportunities Analysis gives you an instant access to your opportunities with information such as the expected revenue, planned cost, missed deadlines or the number of interactions per opportunity. This report is mainly used by the sales manager in order to do the periodic review with the teams of the sales pipeline.</field>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_opportunity_graph">
<field name="sequence" eval="2"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="view_report_crm_opportunity_graph"/>
<field name="act_window_id" ref="action_report_crm_opportunity"/>
</record>
<menuitem name="Leads Analysis" id="menu_report_crm_leads_tree"
groups="base.group_sale_manager"
parent="base.next_id_64" action="action_report_crm_lead" sequence="1"/>
<menuitem name="Opportunities Analysis" id="menu_report_crm_opportunities_tree"
parent="base.next_id_64" action="action_report_crm_opportunity" sequence="5"/>

View File

@ -52,7 +52,7 @@
</group>
<group string="Categorization">
<field name="priority"/>
<field name="categ_id" domain="[('object_id.model', '=', 'crm.helpdesk')]"/>
<field name="categ_id" domain="[('object_id.model', '=', 'crm.helpdesk')]" context="{'object_name': 'crm.helpdesk'}"/>
<field name="channel_id"/>
</group>
</group>

View File

@ -194,9 +194,9 @@ class delivery_grid(osv.osv):
for line in order.order_line:
if not line.product_id or line.is_delivery:
continue
total += line.price_subtotal or 0.0
weight += (line.product_id.weight or 0.0) * line.product_uom_qty
volume += (line.product_id.volume or 0.0) * line.product_uom_qty
total = order.amount_total or 0.0
return self.get_price_from_picking(cr, uid, id, total,weight, volume, context=context)

View File

@ -13,8 +13,8 @@ class EDI(openerp.http.Controller):
modules = webmain.module_boot(req) + ['edi']
modules_str = ','.join(modules)
modules_json = simplejson.dumps(modules)
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in webmain.manifest_list(req, modules_str, 'js'))
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in webmain.manifest_list(req, modules_str, 'css'))
css = '<link rel="stylesheet" href="/web/css/web.assets_backend"/>'
js = '<script type="text/javascript" src="/web/js/web.assets_backend"></script>'
# `url` may contain a full URL with a valid query string, we basically want to watch out for XML brackets and double-quotes
safe_url = werkzeug.url_quote_plus(url,':/?&;=')

View File

@ -431,7 +431,7 @@ class email_template(osv.osv):
is taken from template definition)
:returns: a dict containing all relevant fields for creating a new
mail.mail entry, with one extra key ``attachments``, in the
format expected by :py:meth:`mail_thread.message_post`.
format [(report_name, data)] where data is base64 encoded.
"""
if context is None:
context = {}
@ -492,7 +492,8 @@ class email_template(osv.osv):
result, format = self.pool['report'].get_pdf(cr, uid, [res_id], report_service, context=ctx), 'pdf'
else:
result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
# TODO in trunk, change return format to binary to match message_post expected format
result = base64.b64encode(result)
if not report_name:
report_name = 'report.' + report_service

View File

@ -62,7 +62,7 @@ class test_message_compose(TestMail):
'name': 'Pigs Template',
'subject': '${object.name}',
'body_html': '${object.description}',
'user_signature': True,
'user_signature': False,
'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])],
'email_to': 'b@b.b, c@c.c',
'email_cc': 'd@d.d'
@ -157,7 +157,7 @@ class test_message_compose(TestMail):
message_pids = [partner.id for partner in compose.partner_ids]
partner_ids = [p_a_id]
self.assertEqual(compose.subject, '${object.name}', 'mail.compose.message subject incorrect')
self.assertEqual(compose.body, '<p>${object.description}</p>', 'mail.compose.message body incorrect')
self.assertEqual(compose.body, '<p>${object.description}</p>', 'mail.compose.message body incorrect') # todo: check signature
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
# 2. Post the comment, get created message

View File

@ -87,9 +87,16 @@ class mail_compose_message(osv.TransientModel):
""" - mass_mailing: we cannot render, so return the template values
- normal mode: return rendered values """
if template_id and composition_mode == 'mass_mail':
fields = ['subject', 'body_html', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id']
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
fields = ['subject', 'body_html', 'email_from', 'reply_to', 'mail_server_id']
template = self.pool['email.template'].browse(cr, uid, template_id, context=context)
values = dict((field, getattr(template, field)) for field in fields if getattr(template, field))
if template.attachment_ids:
values['attachment_ids'] = [att.id for att in template.attachment_ids]
if template.mail_server_id:
values['mail_server_id'] = template.mail_server_id.id
if template.user_signature and 'body_html' in values:
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
elif template_id:
values = self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context=context)[res_id]
# transform attachments into attachment_ids; not attached to the document because this will

View File

@ -131,7 +131,7 @@ class event_event(osv.osv):
@return: Dictionary of function field values.
"""
event_ids=set()
for registration in self.browse(cr, uid, ids, context=context):
for registration in self.pool['event.registration'].browse(cr, uid, ids, context=context):
event_ids.add(registration.event_id.id)
return list(event_ids)
@ -317,6 +317,7 @@ class event_registration(osv.osv):
def confirm_registration(self, cr, uid, ids, context=None):
for reg in self.browse(cr, uid, ids, context=context or {}):
self.pool.get('event.event').message_post(cr, uid, [reg.event_id.id], body=_('New registration confirmed: %s.') % (reg.name or '', ),subtype="event.mt_event_registration", context=context)
self.message_post(cr, uid, reg.id, body=_('Event Registration confirmed.'), context=context)
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
def registration_open(self, cr, uid, ids, context=None):

View File

@ -105,9 +105,9 @@
<field name="phone"/>
<field name="nb_register" />
<field name="state"/>
<button name="registration_open" string="Confirm Registration" states="draft" type="object"/>
<button name="button_reg_close" string="Attended the Event" states="open" type="object"/>
<button name="button_reg_cancel" string="Cancel Registration" states="draft,open" type="object"/>
<button name="registration_open" string="Confirm Registration" states="draft" type="object" icon="gtk-apply"/>
<button name="button_reg_close" string="Attended the Event" states="open" type="object" icon="gtk-jump-to"/>
<button name="button_reg_cancel" string="Cancel Registration" states="draft,open" type="object" icon="gtk-cancel"/>
</tree>
<form string="Registration">
<field name="partner_id" attrs="{'readonly':[('state','!=', 'draft')]}" on_change="onchange_partner_id(partner_id, context)" />
@ -125,9 +125,9 @@
<separator string="" colspan="4"/>
<newline/>
<field name="state" colspan="2"/>
<button name="registration_open" string="Confirm Registration" states="draft" type="object"/>
<button name="button_reg_close" string="Attended the Event" states="open" type="object"/>
<button name="button_reg_cancel" string="Cancel Registration" states="draft,open" type="object"/>
<button name="registration_open" string="Confirm Registration" states="draft" type="object" icon="gtk-apply"/>
<button name="button_reg_close" string="Attended the Event" states="open" type="object" icon="gtk-jump-to"/>
<button name="button_reg_cancel" string="Cancel Registration" states="draft,open" type="object" icon="gtk-cancel"/>
</group>
</form>
</field>
@ -333,9 +333,9 @@
<field name="origin"/>
<field name="state"/>
<field name="message_unread" invisible="1"/>
<button name="registration_open" string="Confirm Registration" states="draft" type="object"/>
<button name="button_reg_close" string="Attended the Event" states="open" type="object"/>
<button name="button_reg_cancel" string="Cancel Registration" states="draft,open" type="object"/>
<button name="registration_open" string="Confirm Registration" states="draft" type="object" icon="gtk-apply"/>
<button name="button_reg_close" string="Attended the Event" states="open" type="object" icon="gtk-jump-to"/>
<button name="button_reg_cancel" string="Cancel Registration" states="draft,open" type="object" icon="gtk-cancel"/>
</tree>
</field>
</record>
@ -367,7 +367,7 @@
<field name="email" class="oe_inline"/>
<button class="oe_inline oe_right" string="Send Email"
name="%(mail.action_email_compose_message_wizard)d"
context= '{"default_email_to":email}' type="action"/>
icon="terp-mail-message-new" context= '{"default_email_to":email}' type="action"/>
</div>
</group>
<group>
@ -434,6 +434,8 @@
<filter string="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Event" name="group_event" domain="[]" context="{'group_by':'event_id'}"/>
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Registration Day" domain="[]" context="{'group_by': 'create_date:day'}"/>
<filter string="Registration Month" domain="[]" context="{'group_by': 'create_date:month'}"/>
</group>
</search>
</field>

View File

@ -19,6 +19,7 @@
#
##############################################################################
from openerp.addons.event.event import event_event as Event
from openerp.osv import fields, osv
from openerp.tools.translate import _
@ -137,6 +138,17 @@ class event_event(osv.osv):
pass
return []
def _get_ticket_events(self, cr, uid, ids, context=None):
# `self` is the event.event.ticket model when called by ORM!
return list(set(ticket.event_id.id
for ticket in self.browse(cr, uid, ids, context)))
# proxy method, can't import parent method directly as unbound_method: it would receive
# an invalid `self` <event_registration> when called by ORM
def _events_from_registrations(self, cr, uid, ids, context=None):
# `self` is the event.registration model when called by ORM
return self.pool['event.event']._get_events_from_registrations(cr, uid, ids, context=context)
_columns = {
'event_ticket_ids': fields.one2many('event.event.ticket', "event_id", "Event Ticket"),
'seats_max': fields.function(_get_seats_max,
@ -144,7 +156,15 @@ class event_event(osv.osv):
help="The maximum registration level is equal to the sum of the maximum registration of event ticket." +
"If you have too much registrations you are not able to confirm your event. (0 to ignore this rule )",
type='integer',
readonly=True)
readonly=True),
'seats_available': fields.function(Event._get_seats, oldname='register_avail', string='Available Seats',
type='integer', multi='seats_reserved',
store={
'event.registration': (_events_from_registrations, ['state'], 10),
'event.event': (lambda self, cr, uid, ids, c = {}: ids,
['seats_max', 'registration_ids'], 20),
'event.event.ticket': (_get_ticket_events, ['seats_max'], 10),
}),
}
_defaults = {
'event_ticket_ids': _get_tickets
@ -167,12 +187,23 @@ class event_ticket(osv.osv):
if ticket.seats_max > 0 else None
return res
def _is_expired(self, cr, uid, ids, field_name, args, context=None):
# FIXME: A ticket is considered expired when the deadline is passed. The deadline should
# be considered in the timezone of the event, not the timezone of the user!
# Until we add a TZ on the event we'll use the context's current date, more accurate
# than using UTC all the time.
current_date = fields.date.context_today(self, cr, uid, context=context)
return {ticket.id: ticket.deadline and ticket.deadline < current_date
for ticket in self.browse(cr, uid, ids, context=context)}
_columns = {
'name': fields.char('Name', size=64, required=True, translate=True),
'event_id': fields.many2one('event.event', "Event", required=True, ondelete='cascade'),
'product_id': fields.many2one('product.product', 'Product', required=True, domain=[("event_type_id", "!=", False)]),
'registration_ids': fields.one2many('event.registration', 'event_ticket_id', 'Registrations'),
'deadline': fields.date("Sales End"),
'is_expired': fields.function(_is_expired, type='boolean', string='Is Expired'),
'price': fields.float('Price'),
'seats_max': fields.integer('Maximum Avalaible Seats', oldname='register_max', help="You can for each event define a maximum registration level. If you have too much registrations you are not able to confirm your event. (put 0 to ignore this rule )"),
'seats_reserved': fields.function(_get_seats, string='Reserved Seats', type='integer', multi='seats_reserved'),

View File

@ -39,6 +39,17 @@
</field>
</record>
<record model="ir.ui.view" id="view_event_registration_ticket_form">
<field name="name">event.registration.ticket.form</field>
<field name="model">event.registration</field>
<field name="inherit_id" ref="event.view_event_registration_form" />
<field name="arch" type="xml">
<field name="user_id" position="after">
<field name="event_ticket_id" domain="[('event_id', '=', event_id)]"/>
</field>
</field>
</record>
<record model="ir.ui.view" id="event_sale_product_template_form">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view" />

View File

@ -41,10 +41,6 @@
<field name="description">Badge Granted</field>
</record>
</data>
<data noupdate="0">
<record id="email_template_badge_received" model="email.template">
<field name="name">Received Badge</field>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<data noupdate="1">
<record forcecreate="True" id="ir_cron_check_challenge"
model="ir.cron">
<field name="name">Run Goal Challenge Checker</field>

View File

@ -90,10 +90,6 @@
]]></field>
</record>
</data>
<data>
<!-- goal definitions -->
<record model="gamification.goal.definition" id="definition_base_timezone">
<field name="name">Set your Timezone</field>

View File

@ -200,7 +200,6 @@ class gamification_challenge(osv.Model):
'visibility_mode': 'personal',
'report_message_frequency': 'never',
'last_report_date': fields.date.today,
'start_date': fields.date.today,
'manager_id': lambda s, cr, uid, c: uid,
'category': 'hr',
'reward_failure': False,
@ -269,13 +268,15 @@ class gamification_challenge(osv.Model):
planned_challenge_ids = self.search(cr, uid, [
('state', '=', 'draft'),
('start_date', '<=', fields.date.today())])
self.write(cr, uid, planned_challenge_ids, {'state': 'inprogress'}, context=context)
if planned_challenge_ids:
self.write(cr, uid, planned_challenge_ids, {'state': 'inprogress'}, context=context)
# close scheduled challenges
planned_challenge_ids = self.search(cr, uid, [
('state', '=', 'inprogress'),
('end_date', '>=', fields.date.today())])
self.write(cr, uid, planned_challenge_ids, {'state': 'done'}, context=context)
if planned_challenge_ids:
self.write(cr, uid, planned_challenge_ids, {'state': 'done'}, context=context)
if not ids:
ids = self.search(cr, uid, [('state', '=', 'inprogress')], context=context)

View File

@ -39,5 +39,12 @@
<field name="domain_force">[(1, '=', 1)]</field>
</record>
<record id="goal_global_multicompany" model="ir.rule">
<field name="name">User can only see his/her goals or goal from the same challenge in board visibility</field>
<field name="model_id" ref="model_gamification_goal"/>
<field name="domain_force">[('user_id.company_id', 'child_of', [user.company_id.id])]</field>
<field name="global" eval="True"/>
</record>
</data>
</openerp>

View File

@ -118,7 +118,7 @@ openerp.gamification = function(instance) {
}
});
instance.mail.Widget.include({
instance.mail.Wall.include({
start: function() {
this._super();
var sidebar = new instance.gamification.Sidebar(this);

View File

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<openerp>
<data>
<data noupdate="1">
<!-- goal definitions -->
<record model="gamification.goal.definition" id="definition_crm_tot_invoices">

View File

@ -4,7 +4,6 @@
<!-- challenges -->
<record model="gamification.challenge" id="challenge_crm_sale">
<field name="user_ids" eval="[(4,ref('base.user_demo'))]" />
<field name="state">inprogress</field>
</record>

View File

@ -28,7 +28,7 @@ Get all your HR operations managed easily: knowledge sharing, recruitments, appr
<h2 class="oe_slogan">Manage Your Employees</h2>
<div class="oe_span6">
<p class='oe_mt32'>
Oversee all important information in your company address book. Some information are restricted to HR managers, others are public to easily find colleagues.
Oversee all important information in your company address book. Some information are restricted to HR managers, others are public to easily look colleagues.
</p><p>
Record employee contracts and get alerts when they have to be renewed.
</p>
@ -154,7 +154,7 @@ Inspire achievement with challenges, goals and rewards. Define clear objectives
</p>
</div>
<div class="oe_span4 oe_centered">
<h3>Personnal Objectives</h3>
<h3>Personal Objectives</h3>
<div class="oe_row_img">
<img class="oe_picture" src="crm_game_02.png">
</div>

View File

@ -10,19 +10,7 @@
<record id="survey.menu_surveys" model="ir.ui.menu">
<field eval="[(4,ref('base.group_hr_manager'))]" name="groups_id"/>
</record>
<record id="survey.menu_define_survey" model="ir.ui.menu">
<field eval="[(4,ref('base.group_hr_manager'))]" name="groups_id"/>
</record>
<record id="survey.menu_survey_form" model="ir.ui.menu">
<field eval="[(4,ref('base.group_hr_manager'))]" name="groups_id"/>
</record>
<record id="survey.menu_survey_page_form1" model="ir.ui.menu">
<field eval="[(4,ref('base.group_hr_manager'))]" name="groups_id"/>
</record>
<record id="survey.menu_survey_type_form1" model="ir.ui.menu">
<field eval="[(4,ref('base.group_hr_manager'))]" name="groups_id"/>
</record>
<record id="survey.menu_answer_surveys" model="ir.ui.menu">
<record id="survey.menu_surveys_configuration" model="ir.ui.menu">
<field eval="[(4,ref('base.group_hr_manager'))]" name="groups_id"/>
</record>

View File

@ -44,7 +44,7 @@
<td>
<span t-field="line.name"/>
<span t-field="line.description"/><br/>
<span t-field="line.analytic_account.complete_name"/>
<span t-field="line.analytic_account.complete_name" t-if="line.analytic_account"/>
</td>
<td style="text-center">
<span t-field="line.ref"/>

View File

@ -20,10 +20,11 @@
##############################################################################
import time
from datetime import datetime, timedelta
from datetime import datetime
from dateutil.relativedelta import relativedelta
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.translate import _
class hr_timesheet_sheet(osv.osv):
@ -62,28 +63,56 @@ class hr_timesheet_sheet(osv.osv):
def copy(self, cr, uid, ids, *args, **argv):
raise osv.except_osv(_('Error!'), _('You cannot duplicate a timesheet.'))
def create(self, cr, uid, vals, *args, **argv):
def create(self, cr, uid, vals, context=None):
if 'employee_id' in vals:
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).user_id:
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id'], context=context).user_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link him/her to a user.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).product_id:
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id'], context=context).product_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link the employee to a product, like \'Consultant\'.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).journal_id:
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id'], context=context).journal_id:
raise osv.except_osv(_('Configuration Error!'), _('In order to create a timesheet for this employee, you must assign an analytic journal to the employee, like \'Timesheet Journal\'.'))
return super(hr_timesheet_sheet, self).create(cr, uid, vals, *args, **argv)
if vals.get('attendances_ids'):
# If attendances, we sort them by date asc before writing them, to satisfy the alternance constraint
vals['attendances_ids'] = self.sort_attendances(cr, uid, vals['attendances_ids'], context=context)
return super(hr_timesheet_sheet, self).create(cr, uid, vals, context=context)
def write(self, cr, uid, ids, vals, *args, **argv):
def write(self, cr, uid, ids, vals, context=None):
if 'employee_id' in vals:
new_user_id = self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).user_id.id or False
new_user_id = self.pool.get('hr.employee').browse(cr, uid, vals['employee_id'], context=context).user_id.id or False
if not new_user_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link him/her to a user.'))
if not self._sheet_date(cr, uid, ids, forced_user_id=new_user_id):
if not self._sheet_date(cr, uid, ids, forced_user_id=new_user_id, context=context):
raise osv.except_osv(_('Error!'), _('You cannot have 2 timesheets that overlap!\nYou should use the menu \'My Timesheet\' to avoid this problem.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).product_id:
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id'], context=context).product_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link the employee to a product.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).journal_id:
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id'], context=context).journal_id:
raise osv.except_osv(_('Configuration Error!'), _('In order to create a timesheet for this employee, you must assign an analytic journal to the employee, like \'Timesheet Journal\'.'))
return super(hr_timesheet_sheet, self).write(cr, uid, ids, vals, *args, **argv)
if vals.get('attendances_ids'):
# If attendances, we sort them by date asc before writing them, to satisfy the alternance constraint
# In addition to the date order, deleting attendances are done before inserting attendances
vals['attendances_ids'] = self.sort_attendances(cr, uid, vals['attendances_ids'], context=context)
res = super(hr_timesheet_sheet, self).write(cr, uid, ids, vals, context=context)
if vals.get('attendances_ids'):
for timesheet in self.browse(cr, uid, ids):
if not self.pool['hr.attendance']._altern_si_so(cr, uid, [att.id for att in timesheet.attendances_ids]):
raise osv.except_osv(_('Warning !'), _('Error ! Sign in (resp. Sign out) must follow Sign out (resp. Sign in)'))
return res
def sort_attendances(self, cr, uid, attendance_tuples, context=None):
date_attendances = []
for att_tuple in attendance_tuples:
if att_tuple[0] in [0,1,4]:
if att_tuple[0] in [0,1]:
name = att_tuple[2]['name']
else:
name = self.pool['hr.attendance'].browse(cr, uid, att_tuple[1]).name
date_attendances.append((1, name, att_tuple))
elif att_tuple[0] in [2,3]:
date_attendances.append((0, self.pool['hr.attendance'].browse(cr, uid, att_tuple[1]).name, att_tuple))
else:
date_attendances.append((0, False, att_tuple))
date_attendances.sort()
return [att[2] for att in date_attendances]
def button_confirm(self, cr, uid, ids, context=None):
for sheet in self.browse(cr, uid, ids, context=context):
@ -368,18 +397,22 @@ class hr_attendance(osv.osv):
attendance_ids.extend([row[0] for row in cr.fetchall()])
return attendance_ids
def _get_current_sheet(self, cr, uid, employee_id, date=False, context=None):
if not date:
date = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
# ending date with no time to avoid timesheet with early date_to
date_to = date[0:10]+' 00:00:00'
# limit=1 because only one sheet possible for an employee between 2 dates
sheet_ids = self.pool.get('hr_timesheet_sheet.sheet').search(cr, uid, [
('date_to', '>=', date_to), ('date_from', '<=', date),
('employee_id', '=', employee_id)
], limit=1, context=context)
return sheet_ids and sheet_ids[0] or False
def _sheet(self, cursor, user, ids, name, args, context=None):
sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
res = {}.fromkeys(ids, False)
for attendance in self.browse(cursor, user, ids, context=context):
date_to = datetime.strftime(datetime.strptime(attendance.name[0:10], '%Y-%m-%d'), '%Y-%m-%d %H:%M:%S')
sheet_ids = sheet_obj.search(cursor, user,
[('date_to', '>=', date_to), ('date_from', '<=', attendance.name),
('employee_id', '=', attendance.employee_id.id)],
context=context)
if sheet_ids:
# [0] because only one sheet possible for an employee between 2 dates
res[attendance.id] = sheet_obj.name_get(cursor, user, sheet_ids, context=context)[0]
res[attendance.id] = self._get_current_sheet(cursor, user, attendance.employee_id.id, attendance.name, context=context)
return res
_columns = {
@ -398,16 +431,15 @@ class hr_attendance(osv.osv):
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
if 'sheet_id' in context:
ts = self.pool.get('hr_timesheet_sheet.sheet').browse(cr, uid, context['sheet_id'], context=context)
sheet_id = context.get('sheet_id') or self._get_current_sheet(cr, uid, vals.get('employee_id'), vals.get('name'), context=context)
if sheet_id:
ts = self.pool.get('hr_timesheet_sheet.sheet').browse(cr, uid, sheet_id, context=context)
if ts.state not in ('draft', 'new'):
raise osv.except_osv(_('Error!'), _('You cannot modify an entry in a confirmed timesheet.'))
res = super(hr_attendance,self).create(cr, uid, vals, context=context)
if 'sheet_id' in context:
if context['sheet_id'] != self.browse(cr, uid, res, context=context).sheet_id.id:
raise osv.except_osv(_('User Error!'), _('You cannot enter an attendance ' \
'date outside the current timesheet dates.'))
return res
raise osv.except_osv(_('Error!'), _('You can not enter an attendance in a submitted timesheet. Ask your manager to reset it before adding attendance.'))
elif ts.date_from > vals.get('name') or ts.date_to < vals.get('name'):
raise osv.except_osv(_('User Error!'), _('You can not enter an attendance date outside the current timesheet dates.'))
return super(hr_attendance,self).create(cr, uid, vals, context=context)
def unlink(self, cr, uid, ids, *args, **kwargs):
if isinstance(ids, (int, long)):

View File

@ -17,38 +17,14 @@
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
<record id="lunch_mind_your_own_food" model="ir.rule">
<field name="name">lunch.order: do not see and create other people's order</field>
<field name="model_id" ref="model_lunch_order"/>
<field name="groups" eval="[(4, ref('group_lunch_user'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
</record>
<record id="lunch_mind_other_food" model="ir.rule">
<field name="name">lunch.order: do not see and create other people's order</field>
<field name="model_id" ref="model_lunch_order"/>
<field name="groups" eval="[(4, ref('group_lunch_manager'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
<record id="lunch_mind_your_own_food_line" model="ir.rule">
<field name="name">lunch.order.line: do not see and create other people's order line</field>
<field name="model_id" ref="model_lunch_order_line"/>
<field name="groups" eval="[(4, ref('group_lunch_user'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
</record>
<record id="lunch_mind_other_food_line" model="ir.rule">
<field name="name">lunch.order.line: do not see and create other people's order line</field>
<field name="model_id" ref="model_lunch_order_line"/>
<field name="groups" eval="[(4, ref('group_lunch_manager'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
<record id="lunch_mind_your_own_food_money" model="ir.rule">
<field name="name">lunch.cashmove: do not see and create other people's cashmove</field>
<field name="name">lunch.cashmove: do not see other people's cashmove</field>
<field name="model_id" ref="model_lunch_cashmove"/>
<field name="groups" eval="[(4, ref('group_lunch_user'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
</record>
<record id="lunch_mind_other_food_money" model="ir.rule">
<field name="name">lunch.cashmove: do not see and create other people's cashmove</field>
<field name="name">lunch.cashmove: do see other people's cashmove</field>
<field name="model_id" ref="model_lunch_cashmove"/>
<field name="groups" eval="[(4, ref('group_lunch_manager'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>

View File

@ -204,6 +204,7 @@ class mail_alias(osv.Model):
alias_vals['alias_defaults'] = dict((k, obj_data[v]) for k, v in alias_defaults.iteritems())
alias_vals['alias_parent_thread_id'] = obj_data['id']
alias_create_ctx = dict(context, alias_model_name=alias_model_name, alias_parent_model_name=child_model_name)
print 'EEEEEEEEEEEe',alias_vals, alias_create_ctx
alias_id = mail_alias.create(cr, SUPERUSER_ID, alias_vals, context=alias_create_ctx)
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id}, context={'mail_notrack': True})
_logger.info('Mail alias created for %s %s (id %s)', child_model_name, obj_data[alias_key], obj_data['id'])

View File

@ -135,7 +135,7 @@ class mail_mail(osv.Model):
:param browse_record mail: the mail that was just sent
:return: True
"""
if mail.auto_delete:
if mail_sent and mail.auto_delete:
# done with SUPERUSER_ID to avoid giving large unlink access rights
self.unlink(cr, SUPERUSER_ID, [mail.id], context=context)
return True

View File

@ -92,7 +92,7 @@
<field name="model">mail.mail</field>
<field name="arch" type="xml">
<search string="Email Search">
<field name="email_from" filter_domain="['|' '|',('email_from','ilike',self), ('email_to','ilike',self), ('subject','ilike',self)]" string="Email"/>
<field name="email_from" filter_domain="['|', '|',('email_from','ilike',self), ('email_to','ilike',self), ('subject','ilike',self)]" string="Email"/>
<field name="date"/>
<filter icon="terp-camera_test" name="received" string="Received" domain="[('state','=','received')]"/>
<filter icon="terp-call-start" name="outgoing" string="Outgoing" domain="[('state','=','outgoing')]"/>

View File

@ -14,7 +14,7 @@ class MassMailController(http.Controller):
mail_mail_stats.set_opened(request.cr, SUPERUSER_ID, mail_mail_ids=[mail_id])
response = werkzeug.wrappers.Response()
response.mimetype = 'image/gif'
response.set_data('R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='.decode('base64'))
response.data = 'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='.decode('base64')
return response
@http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', auth='none')

View File

@ -271,9 +271,9 @@ class MassMailing(osv.Model):
date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
domain = [('mass_mailing_id', '=', mailing.id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)]
res[mailing.id]['opened_dayly'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['opened'], 'opened_count', 'opened:day', date_begin, context=context))
res[mailing.id]['opened_daily'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['opened'], 'opened_count', 'opened:day', date_begin, context=context))
domain = [('mass_mailing_id', '=', mailing.id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)]
res[mailing.id]['replied_dayly'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['replied'], 'replied_count', 'replied:day', date_begin, context=context))
res[mailing.id]['replied_daily'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['replied'], 'replied_count', 'replied:day', date_begin, context=context))
return res
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
@ -314,7 +314,7 @@ class MassMailing(osv.Model):
'name': fields.char('Subject', required=True),
'email_from': fields.char('From', required=True),
'create_date': fields.datetime('Creation Date'),
'sent_date': fields.datetime('Sent Date'),
'sent_date': fields.datetime('Sent Date', oldname='date'),
'body_html': fields.html('Body'),
'attachment_ids': fields.many2many(
'ir.attachment', 'mass_mailing_ir_attachments_rel',
@ -340,7 +340,7 @@ class MassMailing(osv.Model):
'reply_to': fields.char('Reply To', help='Preferred Reply-To Address'),
# recipients
'mailing_model': fields.selection(_mailing_model, string='Recipients Model', required=True),
'mailing_domain': fields.char('Domain'),
'mailing_domain': fields.char('Domain', oldname='domain'),
'contact_list_ids': fields.many2many(
'mail.mass_mailing.list', 'mail_mass_mailing_list_rel',
string='Mailing Lists',
@ -398,16 +398,14 @@ class MassMailing(osv.Model):
_get_statistics, string='Replied Ratio',
type='integer', multi='_get_statistics',
),
# dayly ratio
'opened_dayly': fields.function(
# daily ratio
'opened_daily': fields.function(
_get_daily_statistics, string='Opened',
type='char', multi='_get_daily_statistics',
oldname='opened_monthly',
),
'replied_dayly': fields.function(
'replied_daily': fields.function(
_get_daily_statistics, string='Replied',
type='char', multi='_get_daily_statistics',
oldname='replied_monthly',
)
}

View File

@ -19,23 +19,8 @@
#
##############################################################################
from datetime import datetime
from dateutil import relativedelta
import random
try:
import simplejson as json
except ImportError:
import json
import urllib
import urlparse
from openerp import tools
from openerp.exceptions import Warning
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
from openerp.osv import osv, fields
class MailMailStats(osv.Model):
""" MailMailStats models the statistics collected about emails. Those statistics
are stored in a separated model and table to avoid bloating the mail_mail table
@ -48,11 +33,7 @@ class MailMailStats(osv.Model):
_order = 'message_id'
_columns = {
'mail_mail_id': fields.integer(
'Mail ID',
help='ID of the related mail_mail. This field is an integer field because'
'the related mail_mail can be deleted separately from its statistics.'
),
'mail_mail_id': fields.many2one('mail.mail', 'Mail ID', ondelete='set null'),
'message_id': fields.char('Message-ID'),
'model': fields.char('Document model'),
'res_id': fields.integer('Document ID'),
@ -106,4 +87,3 @@ class MailMailStats(osv.Model):
stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('bounced', '=', False)], context)
self.write(cr, uid, stat_ids, {'bounced': fields.datetime.now()}, context=context)
return stat_ids

View File

@ -215,11 +215,11 @@
</button>
<button name="%(action_view_mass_mailing_contacts)d"
type="action" class="oe_stat_button oe_inline">
<field name="opened_dayly" string="Opened Daily" widget="barchart"/>
<field name="opened_daily" string="Opened Daily" widget="barchart"/>
</button>
<button name="%(action_view_mass_mailing_contacts)d"
type="action" class="oe_stat_button oe_inline">
<field name="replied_dayly" string="Replied Daily" widget="barchart"/>
<field name="replied_daily" string="Replied Daily" widget="barchart"/>
</button>
</div>
<div class="oe_button_box" attrs="{'invisible': [('total', '=', 0)]}" style="margin-bottom: 32px">

View File

@ -63,7 +63,7 @@ manufacturing orders.
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Get Flexibility In All Opertions</h2>
<h2 class="oe_slogan">Get Flexibility In All Operations</h2>
<div class="oe_span6">
<p class="oe_mt32">
Edit manually all proposed operations at any level of the progress.
@ -110,7 +110,7 @@ meets your delivery schedule dates.
<div class="oe_span12">
<p>
Organize manufacturing orders and work orders the way you like it. Process next
orders from the list view, control inthe calendar view and edit the proposed
orders from the list view, control in the calendar view and edit the proposed
schedule in the Gantt view.
</p>
</div>

View File

@ -27,8 +27,8 @@
This module allows users to create their own notes inside OpenERP
=================================================================
Use notes to write meeting minutes, organize ideas, organize personnal todo
lists, etc. Each user manages his own personnal Notes. Notes are available to
Use notes to write meeting minutes, organize ideas, organize personal todo
lists, etc. Each user manages his own personal Notes. Notes are available to
their authors only, but they can share notes to others users so that several
people can work on the same note in real time. It's very efficient to share
meeting minutes.

View File

@ -123,16 +123,15 @@ class note_note(osv.osv):
current_stage_ids = self.pool.get('note.stage').search(cr,uid,[('user_id','=',uid)], context=context)
if current_stage_ids: #if the user have some stages
#dict of stages: map les ids sur les noms
stage_name = dict(self.pool.get('note.stage').name_get(cr, uid, current_stage_ids, context=context))
stages = self.pool['note.stage'].browse(cr, uid, current_stage_ids, context=context)
result = [{ #notes by stage for stages user
'__context': {'group_by': groupby[1:]},
'__domain': domain + [('stage_ids.id', '=', current_stage_id)],
'stage_id': (current_stage_id, stage_name[current_stage_id]),
'stage_id_count': self.search(cr,uid, domain+[('stage_ids', '=', current_stage_id)], context=context, count=True)
} for current_stage_id in current_stage_ids]
'__domain': domain + [('stage_ids.id', '=', stage.id)],
'stage_id': (stage.id, stage.name),
'stage_id_count': self.search(cr,uid, domain+[('stage_ids', '=', stage.id)], context=context, count=True),
'__fold': stage.fold,
} for stage in stages]
#note without user's stage
nb_notes_ws = self.search(cr,uid, domain+[('stage_ids', 'not in', current_stage_ids)], context=context, count=True)
@ -148,8 +147,9 @@ class note_note(osv.osv):
result = [{
'__context': {'group_by': groupby[1:]},
'__domain': domain + [dom_not_in],
'stage_id': (current_stage_ids[0], stage_name[current_stage_ids[0]]),
'stage_id_count':nb_notes_ws
'stage_id': (stages[0].id, stages[0].name),
'stage_id_count':nb_notes_ws,
'__fold': stages[0].name,
}] + result
else: # if stage_ids is empty

View File

@ -12,7 +12,7 @@
</div>
<div class="oe_span6">
<p class="oe_mt32">
Organize yourself with efficient todo lists and notes. From personnal tasks to collaborative meeting minutes, increase your user's productivity by giving them the tools to prioritize their work, share their ideas and collaborate on documents.
Organize yourself with efficient todo lists and notes. From personal tasks to collaborative meeting minutes, increase your user's productivity by giving them the tools to prioritize their work, share their ideas and collaborate on documents.
</p>
<div class="oe_centeralign oe_websiteonly">
<a href="http://www.openerp.com/start?app=note" class="oe_button oe_big oe_tacky">Start your <span class="oe_emph">free</span> trial</a>

View File

@ -117,9 +117,9 @@
</div>
<div class='oe_span4 '>
<img class="oe_picture oe_screenshot" src="pos_ui_02.png">
<h3>Blasting fast search</h3>
<h3>Blazing fast search</h3>
<p>
Scan products, browse through hyerarchical categories, or get quick
Scan products, browse through hierarchical categories, or get quick
information about products with the blasting fast filter accross
all your products.
</p>
@ -135,7 +135,7 @@
</div>
<div class='oe_span6'>
<p>
Consolidate all your sales channel in real time: stores, ecommerce, sales teams.
Consolidate all your sales channels in real time: stores, ecommerce, sales teams.
Get real time control of the inventory and accurate forecasts to manage procurements.
</p>
<p>

View File

@ -558,14 +558,14 @@ class stock_warehouse_orderpoint(osv.osv):
]
def default_get(self, cr, uid, fields, context=None):
warehouse_obj = self.pool.get('stock.warehouse')
res = super(stock_warehouse_orderpoint, self).default_get(cr, uid, fields, context)
# default 'warehouse_id' and 'location_id'
if 'warehouse_id' not in res:
warehouse = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'warehouse0', context)
res['warehouse_id'] = warehouse.id
warehouse_ids = res.get('company_id') and warehouse_obj.search(cr, uid, [('company_id', '=', res['company_id'])], limit=1, context=context) or []
res['warehouse_id'] = warehouse_ids and warehouse_ids[0] or False
if 'location_id' not in res:
warehouse = self.pool.get('stock.warehouse').browse(cr, uid, res['warehouse_id'], context)
res['location_id'] = warehouse.lot_stock_id.id
res['location_id'] = res.get('warehouse_id') and warehouse_obj.browse(cr, uid, res['warehouse_id'], context).lot_stock_id.id or False
return res
def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None):

View File

@ -805,9 +805,17 @@ class product_product(osv.osv):
price_type_currency_id = pricetype_obj.browse(cr,uid,price_type_id).currency_id.id
res = {}
# standard_price field can only be seen by users in base.group_user
# Thus, in order to compute the sale price from the cost price for users not in this group
# We fetch the standard price as the superuser
for product in products:
if ptype != 'standard_price':
res[product.id] = product[ptype] or 0.0
else:
res[product.id] = self.read(cr, SUPERUSER_ID, product.id, [ptype], context=context)[ptype] or 0.0
product_uom_obj = self.pool.get('product.uom')
for product in products:
res[product.id] = product[ptype] or 0.0
if ptype == 'list_price':
res[product.id] = (res[product.id] * (product.price_margin or 1.0)) + \
product.price_extra

View File

@ -16,6 +16,7 @@
</record>
<record model="ir.ui.menu" id="base.menu_definitions">
<field name="name">Configuration</field>
<field name="groups_id" eval="[(6,0,[ref('group_project_manager')])]"/>
</record>

View File

@ -142,7 +142,7 @@
<search string="Issue Tracker Search">
<field name="name" string="Issue" filter_domain="['|', '|', '|', ('partner_id','child_of',self), ('description','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/>
<field name="id"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="partner_id" operator="child_of"/>
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
<filter string="New" name="draft" domain="[('stage_id.sequence', '=', 1)]"/>
<separator/>

View File

@ -273,7 +273,7 @@
<filter icon="terp-emblem-important" name="exception" string="Exception" domain="[('state','in',('except_invoice','except_picking'))]" help="Purchase order which are in the exception state"/>
<separator/>
<filter icon="terp-gtk-go-back-rtl" name="not_invoiced" string="Not Invoiced" domain="[('invoice_ids','=', False)]" help="Purchase orders that include lines not invoiced."/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="partner_id" operator="child_of"/>
<field name="product_id"/>
<field name="create_uid"/>
<group expand="0" string="Group By...">
@ -300,7 +300,7 @@
<filter icon="terp-emblem-important" name="exception" string="Exception" domain="[('state','in',('except_invoice','except_picking'))]" help="Purchase orders which are in exception state"/>
<separator/>
<filter icon="terp-gtk-go-back-rtl" name="not_invoiced" string="Not Invoiced" domain="[('invoice_ids','=', False)]" help="Purchase orders that include lines not invoiced."/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="partner_id" operator="child_of"/>
<field name="product_id"/>
<field name="create_uid"/>
<group expand="0" string="Group By...">

View File

@ -32,9 +32,5 @@
<field name="header_spacing">35</field>
<field name="dpi">90</field>
</record>
<record id="base.main_company" model="res.company">
<field name="paperformat_id" ref="paperformat_euro"></field>
</record>
</data>
</openerp>
</openerp>

View File

@ -19,6 +19,9 @@
#
##############################################################################
from functools import partial
from openerp import SUPERUSER_ID
from openerp.osv import osv, fields
@ -114,6 +117,24 @@ class res_company(osv.Model):
_columns = {'paperformat_id': fields.many2one('report.paperformat', 'Paper format')}
def init(self, cr):
# set a default paperformat based on rml one.
ref = partial(self.pool['ir.model.data'].xmlid_to_res_id, cr, SUPERUSER_ID)
ids = self.search(cr, SUPERUSER_ID, [('paperformat_id', '=', False)])
for company in self.browse(cr, SUPERUSER_ID, ids):
paperformat_id = {
'a4': ref('report.paperformat_euro'),
'us_letter': ref('report.paperformat_us'),
}.get(company.rml_paper_format) or ref('report.paperformat_euro')
if paperformat_id:
company.write({'paperformat_id': paperformat_id})
sup = super(res_company, self)
if hasattr(sup, 'init'):
sup.init(cr)
class ir_actions_report(osv.Model):
_inherit = 'ir.actions.report.xml'

View File

@ -107,7 +107,7 @@
<template id="external_layout_footer">
<div class="footer">
<div class="text-center" style="border-top: 1px solid black;">
<ul class="list-inline">
<ul t-if="not company.custom_footer" class="list-inline">
<li t-if="company.phone">Phone: <span t-field="company.phone"/></li>
<li t-if="company.fax and company.phone">&amp;bull;</li>
@ -116,11 +116,15 @@
<li t-if="company.email">&amp;bull;</li>
<li t-if="company.email">Email: <span t-field="company.email"/></li>
<li t-if="website">&amp;bull;</li>
<li t-if="website">Website: <span t-esc="website.name"/></li>
<br/>
<li><span t-field="company.rml_footer"/></li>
<li t-if="company.rml_footer">|</li>
<li t-if="company.website">&amp;bull;</li>
<li t-if="company.website">Website: <span t-field="company.website"/></li>
</ul>
<t t-if="company.custom_footer">
<span t-raw="company.rml_footer"/>
</t>
<ul class="list-inline">
<li>Page:</li>
<li><span class="page"/></li>
<li>/</li>

View File

@ -99,10 +99,8 @@
<field name="model">res.company</field>
<field name="arch" type="xml">
<data>
<xpath expr="//group[@string='Footer Configuration']" position="after">
<group string="QWeb PDF Configuration">
<field name="paperformat_id" />
</group>
<xpath expr="//page[@string='Report Configuration']/group[@string='Configuration']" position="inside">
<field name="paperformat_id" />
</xpath>
</data>
</field>

View File

@ -79,7 +79,7 @@ TODO:
* Web client WYSIWYG
""",
'version': '0.9',
'depends': ['base'],
'depends': ['base','report'],
'author': 'Camptocamp',
'category': 'Reporting', # i.e a technical module, not shown in Application install menu
'url': 'http://http://www.camptocamp.com/',

View File

@ -5,14 +5,12 @@
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<notebook position="inside">
<page string="Webkit">
<separator string="Images" colspan="4"/>
<field name="header_image" colspan="4" nolabel="1"/>
<separator string="Headers" colspan="4"/>
<field name="header_webkit" colspan="4" nolabel="1"/>
</page>
</notebook>
<xpath expr="//page[@string='Report Configuration']/group[@string='Configuration']" position="after">
<group string="Configuration (Webkit)" col="2">
<field name="header_image"/>
<field name="header_webkit"/>
</group>
</xpath>
</field>
</record>
</data>

View File

@ -48,7 +48,7 @@
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_webkit" name="Webkit" parent="base.menu_custom"/>
<menuitem id="menu_webkit" name="Webkit" parent="report.reporting_menuitem"/>
<menuitem action="action_header_img" id="menu_header_img" parent="menu_webkit" sequence="14"/>
<record id="action_header_webkit" model="ir.actions.act_window">

View File

@ -324,7 +324,6 @@ Thanks!</field>
<field name="type">service</field>
<field name="list_price">150.0</field>
<field name="standard_price">100.0</field>
<field name="supply_method">produce</field> <!-- TODO this is a procurement field, which is not a sale dependency -->
<field name="uom_id" ref="product.product_uom_day"/>
<field name="uom_po_id" ref="product.product_uom_day"/>
<field name="company_id" eval="[]"/>

View File

@ -254,7 +254,7 @@
<filter icon="terp-dolar_ok!" string="Done" domain="[('state','=','done')]" help="Sales Order done"/>
<separator/>
<filter string="My" domain="[('user_id','=',uid)]" help="My Sales Orders" icon="terp-personal" name="my_sale_orders_filter"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="partner_id" operator="child_of"/>
<field name="user_id"/>
<field name="section_id" string="Sales Team" groups="base.group_multi_salesteams"/>
<field name="project_id"/>
@ -424,7 +424,7 @@
<separator/>
<filter string="My Sales Order Lines" icon="terp-personnal" domain="[('salesman_id','=',uid)]" help="Sales Order Lines related to a Sales Order of mine"/>
<field name="order_id"/>
<field name="order_partner_id" filter_domain="[('order_partner_id', 'child_of', self)]"/>
<field name="order_partner_id" operator="child_of"/>
<field name="product_id"/>
<field name="salesman_id"/>
<group expand="0" string="Group By...">
@ -450,7 +450,7 @@
<separator/>
<filter string="My Sales Order Lines" icon="terp-personal" domain="[('salesman_id','=',uid)]" help="My Sales Order Lines"/>
<field name="order_id"/>
<field name="order_partner_id" filter_domain="[('order_partner_id', 'child_of', self)]"/>
<field name="order_partner_id" operator="child_of"/>
<field name="product_id"/>
<field name="salesman_id"/>
<group expand="0" string="Group By...">

View File

@ -56,25 +56,6 @@ class SaleLayoutCategory(osv.Model):
'sequence': 10
}
# We want to forbid edit of a category if it is already linked to a report.
def _check(self, cr, uid, ids):
for cat in self.browse(cr, uid, ids):
invoice_obj = self.pool.get('account.invoice.line')
sale_obj = self.pool.get('sale.order.line')
ids = invoice_obj.search(cr, uid, [('sale_layout_cat_id', '=', cat.id)])
ids += sale_obj.search(cr, uid, [('sale_layout_cat_id', '=', cat.id)])
if len(ids) > 0:
return False
return True
_constraints = [(
_check,
'This category could not be modified nor deleted because it is still used in an invoice or'
' a sale report.', ['name']
)]
class AccountInvoice(osv.Model):
_inherit = 'account.invoice'
@ -98,7 +79,7 @@ class AccountInvoiceLine(osv.Model):
_inherit = 'account.invoice.line'
_columns = {
'sale_layout_cat_id': fields.many2one('sale_layout.category',
'Layout Category'),
string='Section'),
'categ_sequence': fields.related('sale_layout_cat_id',
'sequence', type='integer',
string='Layout Sequence', store=True)
@ -128,7 +109,7 @@ class SaleOrderLine(osv.Model):
_inherit = 'sale.order.line'
_columns = {
'sale_layout_cat_id': fields.many2one('sale_layout.category',
'Layout Category'),
string='Section'),
'categ_sequence': fields.related('sale_layout_cat_id',
'sequence', type='integer',
string='Layout Sequence', store=True)

View File

@ -8,20 +8,10 @@
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//notebook/page/field[@name='order_line']/tree" position="attributes">
<attribute name="editable"></attribute>
<xpath expr="//notebook/page/field[@name='order_line']/tree[@string='Sales Order Lines']/field[@name='product_id']" position="after">
<field name="sale_layout_cat_id"/>
</xpath>
</data>
</field>
</record>
<record model="ir.ui.view" id="view_order_form_inherit_2">
<field name="name">sale.order.line.form.inherit_2</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='order_line']/form/group/group/field[@name='product_id']" position="before">
<xpath expr="//field[@name='order_line']/form/group/group/field[@name='product_id']" position="after">
<field name="sale_layout_cat_id"/>
</xpath>
</data>
@ -35,9 +25,10 @@
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//notebook/page/field[@name='invoice_line']/tree" position="attributes">
<attribute name="editable"></attribute>
</xpath>
<xpath expr="//page[@string='Invoice Lines']/field[@name='invoice_line']/tree/field[@name='product_id']" position="after">
<field name="sale_layout_cat_id"/>
</xpath>
</data>
</field>
</record>
@ -48,7 +39,7 @@
<field name="inherit_id" ref="account.view_invoice_line_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//group/group/field[@name='product_id']" position="before">
<xpath expr="//group/group/field[@name='product_id']" position="after">
<field name="sale_layout_cat_id"/>
</xpath>
</data>

View File

@ -356,7 +356,6 @@ class sale_order(osv.osv):
}
def ship_recreate(self, cr, uid, order, line, move_id, proc_id):
# FIXME: deals with potentially cancelled shipments, seems broken (specially if shipment has production lot)
"""
Define ship_recreate for process after shipping exception
param order: sales order to which the order lines belong
@ -365,16 +364,25 @@ class sale_order(osv.osv):
param proc_id: the ID of procurement
"""
move_obj = self.pool.get('stock.move')
if order.state == 'shipping_except':
for pick in order.picking_ids:
for move in pick.move_lines:
if move.state == 'cancel':
mov_ids = move_obj.search(cr, uid, [('state', '=', 'cancel'),('sale_line_id', '=', line.id),('picking_id', '=', pick.id)])
if mov_ids:
for mov in move_obj.browse(cr, uid, mov_ids):
# FIXME: the following seems broken: what if move_id doesn't exist? What if there are several mov_ids? Shouldn't that be a sum?
move_obj.write(cr, uid, [move_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
self.pool.get('procurement.order').write(cr, uid, [proc_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
proc_obj = self.pool.get('procurement.order')
if move_id and order.state == 'shipping_except':
current_move = move_obj.browse(cr, uid, move_id)
moves = []
for picking in order.picking_ids:
if picking.id != current_move.picking_id.id and picking.state != 'cancel':
moves.extend(move for move in picking.move_lines if move.state != 'cancel' and move.sale_line_id.id == line.id)
if moves:
product_qty = current_move.product_qty
product_uos_qty = current_move.product_uos_qty
for move in moves:
product_qty -= move.product_qty
product_uos_qty -= move.product_uos_qty
if product_qty > 0 or product_uos_qty > 0:
move_obj.write(cr, uid, [move_id], {'product_qty': product_qty, 'product_uos_qty': product_uos_qty})
proc_obj.write(cr, uid, [proc_id], {'product_qty': product_qty, 'product_uos_qty': product_uos_qty})
else:
current_move.unlink()
proc_obj.unlink(cr, uid, [proc_id])
return True
def _get_date_planned(self, cr, uid, order, line, start_date, context=None):

View File

@ -54,6 +54,10 @@
<field name="type">make_to_order</field>
</record>
<record id="sale.advance_product_0" model="product.product">
<field name="supply_method">produce</field>
</record>
<!-- Run all schedulers -->
<function model="procurement.order" name="run_scheduler"/>

View File

@ -21,4 +21,4 @@
import sale_team
import sale_team_config
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -19,14 +19,14 @@
#
##############################################################################
{
'name' : 'Sale Team',
'version' : '1.0',
'author' : 'OpenERP SA',
'category': 'Sales Management',
'name': 'Sale Team',
'version': '1.0',
'author': 'OpenERP SA',
'category': 'Sales Management',
'summary': 'Sales Team',
'description': """ """,
'website': 'http://www.openerp.com',
'depends' : ['base','mail','web_kanban_sparkline',],
'depends': ['base','mail','web_kanban_sparkline',],
'data': ['security/sale_team_security.xml',
'security/ir.model.access.csv',
'res_config_view.xml',

View File

@ -18,13 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import calendar
from datetime import date, datetime
from dateutil import relativedelta
from openerp import tools
from openerp.osv import fields
from openerp.osv import osv
from openerp.osv import fields, osv
class crm_case_section(osv.osv):
_name = "crm.case.section"
@ -35,9 +33,8 @@ class crm_case_section(osv.osv):
def get_full_name(self, cr, uid, ids, field_name, arg, context=None):
return dict(self.name_get(cr, uid, ids, context=context))
def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None):
""" Generic method to generate data for bar chart values using SparklineBarWidget.
This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
@ -65,7 +62,7 @@ class crm_case_section(osv.osv):
month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field, 0)}
return section_result
_columns = {
'name': fields.char('Sales Team', size=64, required=True, translate=True),
'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True),
@ -82,7 +79,7 @@ class crm_case_section(osv.osv):
'working_hours': fields.float('Working Hours', digits=(16, 2)),
'color': fields.integer('Color Index'),
}
_defaults = {
'active': 1,
}
@ -110,9 +107,15 @@ class crm_case_section(osv.osv):
name = record['parent_id'][1] + ' / ' + name
res.append((record['id'], name))
return res
class res_partner(osv.Model):
_inherit = 'res.partner'
_columns = {
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
}
class res_users(osv.Model):
_inherit = 'res.users'
_columns = {
@ -124,4 +127,4 @@ class res_users(osv.Model):
# duplicate list to avoid modifying the original reference
self.SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS)
self.SELF_WRITEABLE_FIELDS.extend(['default_section_id'])
return init_res
return init_res

View File

@ -19,9 +19,9 @@
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
class sala_team_configuration(osv.TransientModel):
_name = 'sale.team.config.settings'
_inherit = ['sale.config.settings', 'fetchmail.config.settings']
@ -57,4 +57,4 @@ class sala_team_configuration(osv.TransientModel):
'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams",
implied_group='base.group_multi_salesteams',
help="""Allows you to use Sales Teams to manage your leads and opportunities."""),
}
}

View File

@ -69,7 +69,7 @@ planning and jobs with the scheduler to reduce your process time.
<p class='oe_mt32'>
Get your pickings, packings, receptions and internal moves scheduled
automatically by OpenERP using your own routing rules. Define push and pull
rules to organize a warehouse or to manage produts's moves between several
rules to organize a warehouse or to manage products moves between several
warehouses.
</p>
</div>

View File

@ -718,7 +718,6 @@ class stock_picking(osv.osv):
default = {}
default = default.copy()
picking_obj = self.browse(cr, uid, id, context=context)
move_obj = self.pool.get('stock.move')
if ('name' not in default) or (picking_obj.name == '/'):
seq_obj_name = 'stock.picking.' + picking_obj.type
default['name'] = self.pool.get('ir.sequence').get(cr, uid, seq_obj_name)
@ -727,10 +726,6 @@ class stock_picking(osv.osv):
if 'invoice_state' not in default and picking_obj.invoice_state == 'invoiced':
default['invoice_state'] = '2binvoiced'
res = super(stock_picking, self).copy(cr, uid, id, default, context)
if res:
picking_obj = self.browse(cr, uid, res, context=context)
for move in picking_obj.move_lines:
move_obj.write(cr, uid, [move.id], {'tracking_id': False, 'prodlot_id': False, 'move_history_ids2': [(6, 0, [])], 'move_history_ids': [(6, 0, [])]})
return res
def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
@ -1797,12 +1792,15 @@ class stock_move(osv.osv):
_('Quantities, Units of Measure, Products and Locations cannot be modified on stock moves that have already been processed (except by the Administrator).'))
return super(stock_move, self).write(cr, uid, ids, vals, context=context)
def copy(self, cr, uid, id, default=None, context=None):
def copy_data(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
default = default.copy()
default.update({'move_history_ids2': [], 'move_history_ids': []})
return super(stock_move, self).copy(cr, uid, id, default, context=context)
default.setdefault('tracking_id', False)
default.setdefault('prodlot_id', False)
default.setdefault('move_history_ids', [])
default.setdefault('move_history_ids2', [])
return super(stock_move, self).copy_data(cr, uid, id, default, context=context)
def _auto_init(self, cursor, context=None):
res = super(stock_move, self)._auto_init(cursor, context=context)
@ -2072,41 +2070,46 @@ class stock_move(osv.osv):
if context is None:
context = {}
seq_obj = self.pool.get('ir.sequence')
for picking, todo in self._chain_compute(cr, uid, moves, context=context).items():
ptype = todo[0][1][5] and todo[0][1][5] or location_obj.picking_type_get(cr, uid, todo[0][0].location_dest_id, todo[0][1][0])
if picking:
# name of new picking according to its type
if ptype == 'internal':
new_pick_name = seq_obj.get(cr, uid,'stock.picking')
else :
new_pick_name = seq_obj.get(cr, uid, 'stock.picking.' + ptype)
pickid = self._create_chained_picking(cr, uid, new_pick_name, picking, ptype, todo, context=context)
# Need to check name of old picking because it always considers picking as "OUT" when created from Sales Order
old_ptype = location_obj.picking_type_get(cr, uid, picking.move_lines[0].location_id, picking.move_lines[0].location_dest_id)
if old_ptype != picking.type:
old_pick_name = seq_obj.get(cr, uid, 'stock.picking.' + old_ptype)
self.pool.get('stock.picking').write(cr, uid, [picking.id], {'name': old_pick_name, 'type': old_ptype}, context=context)
else:
pickid = False
for move, (loc, dummy, delay, dummy, company_id, ptype, invoice_state) in todo:
new_id = move_obj.copy(cr, uid, move.id, {
'location_id': move.location_dest_id.id,
'location_dest_id': loc.id,
'date': time.strftime('%Y-%m-%d'),
'picking_id': pickid,
'state': 'waiting',
'company_id': company_id or res_obj._company_default_get(cr, uid, 'stock.company', context=context) ,
'move_history_ids': [],
'date_expected': (datetime.strptime(move.date, '%Y-%m-%d %H:%M:%S') + relativedelta(days=delay or 0)).strftime('%Y-%m-%d'),
'move_history_ids2': []}
)
move_obj.write(cr, uid, [move.id], {
'move_dest_id': new_id,
'move_history_ids': [(4, new_id)]
})
new_moves.append(self.browse(cr, uid, [new_id])[0])
if pickid:
self.pool.get('stock.picking').signal_button_confirm(cr, uid, [pickid])
for picking, chained_moves in self._chain_compute(cr, uid, moves, context=context).items():
# We group the moves by automatic move type, so it creates different pickings for different types
moves_by_type = {}
for move in chained_moves:
moves_by_type.setdefault(move[1][1], []).append(move)
for todo in moves_by_type.values():
ptype = todo[0][1][5] and todo[0][1][5] or location_obj.picking_type_get(cr, uid, todo[0][0].location_dest_id, todo[0][1][0])
if picking:
# name of new picking according to its type
if ptype == 'internal':
new_pick_name = seq_obj.get(cr, uid,'stock.picking')
else :
new_pick_name = seq_obj.get(cr, uid, 'stock.picking.' + ptype)
pickid = self._create_chained_picking(cr, uid, new_pick_name, picking, ptype, todo, context=context)
# Need to check name of old picking because it always considers picking as "OUT" when created from Sales Order
old_ptype = location_obj.picking_type_get(cr, uid, picking.move_lines[0].location_id, picking.move_lines[0].location_dest_id)
if old_ptype != picking.type:
old_pick_name = seq_obj.get(cr, uid, 'stock.picking.' + old_ptype)
self.pool.get('stock.picking').write(cr, uid, [picking.id], {'name': old_pick_name, 'type': old_ptype}, context=context)
else:
pickid = False
for move, (loc, dummy, delay, dummy, company_id, ptype, invoice_state) in todo:
new_id = move_obj.copy(cr, uid, move.id, {
'location_id': move.location_dest_id.id,
'location_dest_id': loc.id,
'date': time.strftime('%Y-%m-%d'),
'picking_id': pickid,
'state': 'waiting',
'company_id': company_id or res_obj._company_default_get(cr, uid, 'stock.company', context=context) ,
'move_history_ids': [],
'date_expected': (datetime.strptime(move.date, '%Y-%m-%d %H:%M:%S') + relativedelta(days=delay or 0)).strftime('%Y-%m-%d'),
'move_history_ids2': []}
)
move_obj.write(cr, uid, [move.id], {
'move_dest_id': new_id,
'move_history_ids': [(4, new_id)]
})
new_moves.append(self.browse(cr, uid, [new_id])[0])
if pickid:
self.pool.get('stock.picking').signal_button_confirm(cr, uid, [pickid])
if new_moves:
new_moves += self.create_chained_picking(cr, uid, new_moves, context)
return new_moves
@ -2996,6 +2999,9 @@ class stock_picking_in(osv.osv):
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
return self.pool.get('stock.picking').read(cr, uid, ids, fields=fields, context=context, load=load)
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
return self.pool['stock.picking'].read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
def check_access_rights(self, cr, uid, operation, raise_exception=True):
#override in order to redirect the check of acces rights on the stock.picking object
return self.pool.get('stock.picking').check_access_rights(cr, uid, operation, raise_exception=raise_exception)
@ -3076,6 +3082,9 @@ class stock_picking_out(osv.osv):
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
return self.pool.get('stock.picking').read(cr, uid, ids, fields=fields, context=context, load=load)
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
return self.pool['stock.picking'].read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
def check_access_rights(self, cr, uid, operation, raise_exception=True):
#override in order to redirect the check of acces rights on the stock.picking object
return self.pool.get('stock.picking').check_access_rights(cr, uid, operation, raise_exception=raise_exception)

View File

@ -1271,7 +1271,7 @@
<field name="date" attrs="{'invisible': [('state', '!=', 'done')]}"/>
</group>
<group string="Traceability"
groups="stock.group_tracking_lot">
groups="stock.group_tracking_lot,stock.group_production_lot">
<label for="tracking_id" groups="stock.group_tracking_lot"/>
<div groups="stock.group_tracking_lot">
<field name="tracking_id" class="oe_inline"/>

View File

@ -28,8 +28,8 @@ class stock_location_product(osv.osv_memory):
_columns = {
'from_date': fields.datetime('From'),
'to_date': fields.datetime('To'),
'type': fields.selection([('inventory','Analyse Current Inventory'),
('period','Analyse a Period')], 'Analyse Type', required=True),
'type': fields.selection([('inventory','Analyze current inventory'),
('period','Analyze period')], 'Analysis Type', required=True),
}
def action_open_window(self, cr, uid, ids, context=None):

View File

@ -284,7 +284,7 @@ class WebsiteSurvey(http.Controller):
# Printing routes
@http.route(['/survey/print/<model("survey.survey"):survey>',
'/survey/print/<model("survey.survey"):survey>/<string:token>'],
type='http', auth='user', multilang=True, website=True)
type='http', auth='public', multilang=True, website=True)
def print_survey(self, survey, token=None, **post):
'''Display an survey in printable view; if <token> is set, it will
grab the answers of the user_input_id that has <token>.'''

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<data noupdate="1">
<!-- Survey users -->
<record model="res.groups" id="base.group_survey_user">
<field name="name">Survey / User</field>
@ -17,7 +17,7 @@
<record id="survey_public_access" model="ir.rule">
<field name="name">Public access to surveys</field>
<field name="model_id" ref="survey.model_survey_survey"/>
<field name="domain_force">[('state', '=', 'open'), ('auth_required', '=', False)]</field>
<field name="domain_force">[('auth_required', '=', False)]</field>
<field name="groups" eval="[(4, ref('base.group_public'))]"/>
<field eval="0" name="perm_unlink"/>
<field eval="0" name="perm_write"/>
@ -53,9 +53,9 @@
<field name="domain_force">[('create_uid', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_public'))]"/>
<field eval="0" name="perm_unlink"/>
<field eval="1" name="perm_write"/>
<field eval="0" name="perm_write"/>
<field eval="1" name="perm_read"/>
<field eval="1" name="perm_create"/>
<field eval="0" name="perm_create"/>
</record>
<record id="survey_rule" model="ir.rule">
@ -64,9 +64,9 @@
<field name="domain_force">[('create_uid', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_survey_user'))]"/>
<field eval="0" name="perm_unlink"/>
<field eval="1" name="perm_write"/>
<field eval="0" name="perm_write"/>
<field eval="1" name="perm_read"/>
<field eval="1" name="perm_create"/>
<field eval="0" name="perm_create"/>
</record>
<record id="survey_rule_manager" model="ir.rule">

View File

@ -6,6 +6,7 @@ import json
import logging
import os
import datetime
import re
from sys import maxint
@ -101,7 +102,9 @@ class Website(openerp.addons.web.controllers.main.Home):
'url': "/page/" + xml_id,
'parent_id': id,
}, context=request.context)
url = "/page/" + xml_id
# Reverse action in order to allow shortcut for /page/<website_xml_id>
url = "/page/" + re.sub(r"^website\.", '', xml_id)
if noredirect:
return werkzeug.wrappers.Response(url, mimetype='text/plain')
return werkzeug.utils.redirect(url)
@ -324,44 +327,30 @@ class Website(openerp.addons.web.controllers.main.Home):
'/website/image',
'/website/image/<model>/<id>/<field>'
], auth="public", website=True)
def website_image(self, model, id, field, max_width=maxint, max_height=maxint):
def website_image(self, model, id, field, max_width=None, max_height=None):
""" Fetches the requested field and ensures it does not go above
(max_width, max_height), resizing it if necessary.
Resizing is bypassed if the object provides a $field_big, which will
be interpreted as a pre-resized version of the base field.
If the record is not found or does not have the requested field,
returns a placeholder image via :meth:`~.placeholder`.
Sets and checks conditional response parameters:
* :mailheader:`ETag` is always set (and checked)
* :mailheader:`Last-Modified is set iif the record has a concurrency
field (``__last_update``)
field (``write_date``)
The requested field is assumed to be base64-encoded image data in
all cases.
"""
Model = request.registry[model]
response = werkzeug.wrappers.Response()
id = int(id)
ids = Model.search(request.cr, request.uid,
[('id', '=', id)], context=request.context)
if not ids and 'website_published' in Model._all_columns:
ids = Model.search(request.cr, openerp.SUPERUSER_ID,
[('id', '=', id), ('website_published', '=', True)], context=request.context)
if not ids:
return self.placeholder(response)
presized = '%s_big' % field
concurrency = '__last_update'
[record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
[concurrency, field, presized],
response = werkzeug.wrappers.Response()
concurrency = 'write_date'
try:
[record] = request.registry[model].read(request.cr, openerp.SUPERUSER_ID, [id],
[concurrency, field],
context=request.context)
except:
return self.placeholder(response)
if concurrency in record:
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
@ -385,30 +374,22 @@ class Website(openerp.addons.web.controllers.main.Home):
if response.status_code == 304:
return response
data = (record.get(presized) or record[field]).decode('base64')
data = record[field].decode('base64')
if (not max_width) and (not max_height):
response.data = data
return response
image = Image.open(cStringIO.StringIO(data))
response.mimetype = Image.MIME[image.format]
# record provides a pre-resized version of the base field, use that
# directly
if record.get(presized):
response.set_data(data)
return response
fit = int(max_width), int(max_height)
w, h = image.size
max_w, max_h = fit
max_w, max_h = int(max_width), int(max_height)
if w < max_w and h < max_h:
response.set_data(data)
response.data = data
else:
image.thumbnail(fit, Image.ANTIALIAS)
image.thumbnail((max_w, max_h), Image.ANTIALIAS)
image.save(response.stream, image.format)
# invalidate content-length computed by make_conditional as
# writing to response.stream does not do it (as of werkzeug 0.9.3)
del response.headers['Content-Length']
return response
#------------------------------------------------------

View File

@ -93,12 +93,13 @@ class ir_http(orm.AbstractModel):
except Exception:
return self._handle_exception(werkzeug.exceptions.NotFound())
generated_path = werkzeug.url_unquote_plus(path)
current_path = werkzeug.url_unquote_plus(request.httprequest.path)
if generated_path != current_path:
if request.lang != request.website.default_lang_code:
path = '/' + request.lang + path
return werkzeug.utils.redirect(path)
if request.httprequest.method in ('GET', 'HEAD'):
generated_path = werkzeug.url_unquote_plus(path)
current_path = werkzeug.url_unquote_plus(request.httprequest.path)
if generated_path != current_path:
if request.lang != request.website.default_lang_code:
path = '/' + request.lang + path
return werkzeug.utils.redirect(path)
def _serve_attachment(self):
domain = [('type', '=', 'binary'), ('url', '=', request.httprequest.path)]
@ -121,7 +122,7 @@ class ir_http(orm.AbstractModel):
return response
response.mimetype = attach[0]['mimetype']
response.set_data(datas.decode('base64'))
response.data = datas.decode('base64')
return response
def _handle_exception(self, exception=None, code=500):

View File

@ -559,9 +559,7 @@ class ir_attachment(osv.osv):
result[attach.id] = urlplus('/website/image', {
'model': 'ir.attachment',
'field': 'datas',
'id': attach.id,
'max_width': 1024,
'max_height': 768,
'id': attach.id
})
return result
def _datas_checksum(self, cr, uid, ids, name, arg, context=None):

View File

@ -1,9 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_website_public,website,website.model_website,,1,0,0,0
access_website,website,website.model_website,base.group_user,1,0,0,0
access_website,website,website.model_website,base.group_website_designer,1,1,1,1
access_website_menu,access_website_menu,model_website_menu,,1,0,0,0
access_website,web menu manager,website.model_website,base.group_website_designer,1,1,1,1
access_website_converter_test,access_website_converter_test,model_website_converter_test,,1,1,1,1
access_website_converter_test_sub,access_website_converter_test_sub,model_website_converter_test_sub,,1,1,1,1
access_website_ir_ui_view,access_website_ir_ui_view,model_ir_ui_view,base.group_website_designer,1,1,1,1
access_seo_public,access_seo_public,model_website_seo_metadata,,1,0,0,0
access_seo_public,access_seo_manager,model_website_seo_metadata,base.group_website_designer,1,1,1,1
access_seo_designer,access_seo_designer,model_website_seo_metadata,base.group_website_designer,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_website_public website website.model_website 1 0 0 0
3 access_website website website.model_website base.group_user base.group_website_designer 1 0 1 0 1 0 1
4 access_website_menu access_website_menu model_website_menu 1 0 0 0
5 access_website web menu manager website.model_website base.group_website_designer 1 1 1 1
6 access_website_converter_test access_website_converter_test model_website_converter_test 1 1 1 1
7 access_website_converter_test_sub access_website_converter_test_sub model_website_converter_test_sub 1 1 1 1
8 access_website_ir_ui_view access_website_ir_ui_view model_ir_ui_view base.group_website_designer 1 1 1 1
9 access_seo_public access_seo_public model_website_seo_metadata 1 0 0 0
10 access_seo_public access_seo_manager model_website_seo_metadata base.group_website_designer 1 1 1 1
11 access_seo_designer access_seo_designer model_website_seo_metadata base.group_website_designer 1 1 1 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Some files were not shown because too many files have changed in this diff Show More