diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py
index 1b378e15e93..d1726d164b6 100644
--- a/addons/account/__openerp__.py
+++ b/addons/account/__openerp__.py
@@ -98,6 +98,7 @@ module named account_voucher.
'wizard/account_reconcile_view.xml',
'wizard/account_reconcile_partner_process_view.xml',
'wizard/account_automatic_reconcile_view.xml',
+ 'wizard/account_financial_report_view.xml',
'project/wizard/project_account_analytic_line_view.xml',
'account_end_fy.xml',
'account_invoice_view.xml',
diff --git a/addons/account/account.py b/addons/account/account.py
index 39e680fa414..38ed96d4aac 100644
--- a/addons/account/account.py
+++ b/addons/account/account.py
@@ -139,7 +139,7 @@ class account_account_type(osv.osv):
'Balance' will generally be used for cash accounts.
'Detail' will copy each existing journal item of the previous year, even the reconciled ones.
'Unreconciled' will copy only the journal items that were unreconciled on the first day of the new fiscal year."""),
- 'sign': fields.selection([(-1, 'Negative'), (1, 'Positive')], 'Sign on Reports', required=True, help='Allows you to change the sign of the balance amount displayed in the reports, so that you can see positive figures instead of negative ones in expenses accounts.'),
+ 'sign': fields.selection([(-1, 'Reverse balance sign'), (1, 'Preserve balance sign')], 'Sign on Reports', required=True, help='For accounts that are typically more debited than credited and that you would like to print as negative amounts in your reports, you should reverse the sign of the balance; e.g.: Expense account. The same applies for accounts that are typically more credited than debited and that you would like to print as positive amounts in your reports; e.g.: Income account.'),
'report_type':fields.selection([
('none','/'),
('income','Profit & Loss (Income Accounts)'),
@@ -431,9 +431,16 @@ class account_account(osv.osv):
return False
return True
+ def _check_account_type(self, cr, uid, ids, context=None):
+ for account in self.browse(cr, uid, ids, context=context):
+ if account.type in ('receivable', 'payable') and account.user_type.close_method != 'unreconciled':
+ return False
+ return True
+
_constraints = [
(_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id']),
- (_check_type, 'Configuration Error! \nYou cannot define children to an account with internal type different of "View"! ', ['type']),
+ (_check_type, 'Configuration Error! \nYou can not define children to an account with internal type different of "View"! ', ['type']),
+ (_check_account_type, 'Configuration Error! \nYou can not select an account type with a deferral method different of "Unreconciled" for accounts with internal type "Payable/Receivable"! ', ['user_type','type']),
]
_sql_constraints = [
('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
@@ -509,14 +516,14 @@ class account_account(osv.osv):
if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
if method == 'write':
- raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
+ raise osv.except_osv(_('Error !'), _('You can not desactivate an account that contains some journal items.'))
elif method == 'unlink':
- raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
+ raise osv.except_osv(_('Error !'), _('You can not remove an account containing journal items!. '))
#Checking whether the account is set as a property to any Partner or not
value = 'account.account,' + str(ids[0])
partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
if partner_prop_acc:
- raise osv.except_osv(_('Warning !'), _('You cannot remove/deactivate an account which is set as a property to any Partner.'))
+ raise osv.except_osv(_('Warning !'), _('You can not remove/desactivate an account which is set on a customer or supplier.'))
return True
def _check_allow_type_change(self, cr, uid, ids, new_type, context=None):
@@ -529,15 +536,20 @@ class account_account(osv.osv):
if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
#Check for 'Closed' type
if old_type == 'closed' and new_type !='closed':
- raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains account entries!"))
+ raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains journal items!"))
#Check for change From group1 to group2 and vice versa
if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1):
- raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from '%s' to '%s' type as it contains account entries!") % (old_type,new_type,))
+ raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from '%s' to '%s' type as it contains journal items!") % (old_type,new_type,))
return True
def write(self, cr, uid, ids, vals, context=None):
+
if context is None:
context = {}
+ if not ids:
+ return True
+ if isinstance(ids, (int, long)):
+ ids = [ids]
# Dont allow changing the company_id when account_move_line already exist
if 'company_id' in vals:
@@ -623,7 +635,7 @@ class account_journal(osv.osv):
'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
'entry_posted': fields.boolean('Skip \'Draft\' State for Manual Entries', help='Check this box if you don\'t want new journal entries to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation. \nNote that journal entries that are automatically created by the system are always skipping that state.'),
'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
- 'allow_date':fields.boolean('Check Date not in the Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'),
+ 'allow_date':fields.boolean('Check Date in Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'),
}
_defaults = {
@@ -650,11 +662,13 @@ class account_journal(osv.osv):
def write(self, cr, uid, ids, vals, context=None):
if context is None:
context = {}
+ if isinstance(ids, (int, long)):
+ ids = [ids]
for journal in self.browse(cr, uid, ids, context=context):
if 'company_id' in vals and journal.company_id.id != vals['company_id']:
move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
if move_lines:
- raise osv.except_osv(_('Warning !'), _('You cannot modify company of this journal as its related record exist in Entry Lines'))
+ raise osv.except_osv(_('Warning !'), _('You can not modify the company of this journal as its related record exist in journal items'))
return super(account_journal, self).write(cr, uid, ids, vals, context=context)
def create_sequence(self, cr, uid, vals, context=None):
@@ -800,8 +814,8 @@ class account_fiscalyear(osv.osv):
return True
_constraints = [
- (_check_duration, 'Error! The duration of the Fiscal Year is invalid. ', ['date_stop']),
- (_check_fiscal_year, 'Error! You cannot define overlapping fiscal years',['date_start', 'date_stop'])
+ (_check_duration, 'Error! The start date of the fiscal year must be before his end date.', ['date_start','date_stop']),
+ (_check_fiscal_year, 'Error! You can not define overlapping fiscal years for the same company.',['date_start', 'date_stop'])
]
def create_period3(self, cr, uid, ids, context=None):
@@ -879,7 +893,10 @@ class account_period(osv.osv):
'state': 'draft',
}
_order = "date_start, special desc"
-
+ _sql_constraints = [
+ ('name_company_uniq', 'unique(name, company_id)', 'The name of the period must be unique per company!'),
+ ]
+
def _check_duration(self,cr,uid,ids,context=None):
obj_period = self.browse(cr, uid, ids[0], context=context)
if obj_period.date_stop < obj_period.date_start:
@@ -952,7 +969,7 @@ class account_period(osv.osv):
if 'company_id' in vals:
move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
if move_lines:
- raise osv.except_osv(_('Warning !'), _('You cannot modify company of this period as its related record exist in Entry Lines'))
+ raise osv.except_osv(_('Warning !'), _('You can not modify company of this period as some journal items exists.'))
return super(account_period, self).write(cr, uid, ids, vals, context=context)
def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
@@ -1180,10 +1197,10 @@ class account_move(osv.osv):
_constraints = [
(_check_centralisation,
- 'You cannot create more than one move per period on centralized journal',
+ 'You can not create more than one move per period on centralized journal',
['journal_id']),
(_check_period_journal,
- 'You cannot create entries on different periods/journals in the same move',
+ 'You can not create journal items on different periods/journals in the same journal entry',
['line_id']),
]
@@ -1194,7 +1211,7 @@ class account_move(osv.osv):
valid_moves = self.validate(cr, uid, ids, context)
if not valid_moves:
- raise osv.except_osv(_('Integrity Error !'), _('You cannot validate a non-balanced entry !\nMake sure you have configured Payment Term properly !\nIt should contain atleast one Payment Term Line with type "Balance" !'))
+ raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non-balanced entry !\nMake sure you have configured payment terms properly !\nThe latest payment term line should be of the type "Balance" !'))
obj_sequence = self.pool.get('ir.sequence')
for move in self.browse(cr, uid, valid_moves, context=context):
if move.name =='/':
@@ -1230,7 +1247,7 @@ class account_move(osv.osv):
if not top:
top = account2.id
elif top<>account2.id:
- raise osv.except_osv(_('Error !'), _('You cannot validate a Journal Entry unless all journal items are in same chart of accounts !'))
+ raise osv.except_osv(_('Error !'), _('You can not validate a journal entry unless all journal items belongs to the same chart of accounts !'))
return self.post(cursor, user, ids, context=context)
def button_cancel(self, cr, uid, ids, context=None):
@@ -1317,7 +1334,7 @@ class account_move(osv.osv):
for move in self.browse(cr, uid, ids, context=context):
if move['state'] != 'draft':
raise osv.except_osv(_('UserError'),
- _('You can not delete posted movement: "%s"!') % \
+ _('You can not delete a posted journal entry "%s"!') % \
move['name'])
line_ids = map(lambda x: x.id, move.line_id)
context['journal_id'] = move.journal_id.id
@@ -1337,7 +1354,7 @@ class account_move(osv.osv):
def _centralise(self, cr, uid, move, mode, context=None):
assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
- currency_obj = self.pool.get('res.currency')
+ currency_obj = self.pool.get('res.currency')
if context is None:
context = {}
@@ -1599,7 +1616,7 @@ class account_tax_code(osv.osv):
(parent_ids,) + where_params)
res=dict(cr.fetchall())
obj_precision = self.pool.get('decimal.precision')
- res2 = {}
+ res2 = {}
for record in self.browse(cr, uid, ids, context=context):
def _rec_get(record):
amount = res.get(record.id, 0.0)
@@ -1768,6 +1785,9 @@ class account_tax(osv.osv):
'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
}
+ _sql_constraints = [
+ ('name_company_uniq', 'unique(name, company_id)', 'Tax Name must be unique per company!'),
+ ]
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
"""
@@ -2126,7 +2146,11 @@ class account_model(osv.osv):
period_id = period_id[0]
for model in self.browse(cr, uid, ids, context=context):
- entry['name'] = model.name%{'year':time.strftime('%Y'), 'month':time.strftime('%m'), 'date':time.strftime('%Y-%m')}
+ try:
+ entry['name'] = model.name%{'year':time.strftime('%Y'), 'month':time.strftime('%m'), 'date':time.strftime('%Y-%m')}
+ except:
+ raise osv.except_osv(_('Wrong model !'), _('You have a wrong expression "%(...)s" in your model !'))
+
move_id = account_move_obj.create(cr, uid, {
'ref': entry['name'],
'period_id': period_id,
@@ -2198,8 +2222,8 @@ class account_model_line(osv.osv):
}
_order = 'sequence'
_sql_constraints = [
- ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'),
- ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'),
+ ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model, they must be positive!'),
+ ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model, they must be positive!'),
]
account_model_line()
@@ -2365,7 +2389,7 @@ class account_account_template(osv.osv):
_check_recursion = check_cycle
_constraints = [
(_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id']),
- (_check_type, 'Configuration Error! \nYou cannot define children to an account with internal type different of "View"! ', ['type']),
+ (_check_type, 'Configuration Error!\nYou can not define children to an account with internal type different of "View"! ', ['type']),
]
@@ -2398,7 +2422,7 @@ class account_add_tmpl_wizard(osv.osv_memory):
ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
res = None
if not ptids or not ptids[0]['code']:
- raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!'))
+ raise osv.except_osv(_('Error !'), _('I can not locate a parent code for the template account!'))
res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
return res and res[0] or False
@@ -2613,7 +2637,88 @@ class account_fiscal_position_account_template(osv.osv):
account_fiscal_position_account_template()
- # Multi charts of Accounts wizard
+# ---------------------------------------------------------
+# Account Financial Report
+# ---------------------------------------------------------
+
+class account_financial_report(osv.osv):
+ _name = "account.financial.report"
+ _description = "Account Report"
+
+ def _get_level(self, cr, uid, ids, field_name, arg, context=None):
+ res = {}
+ for report in self.browse(cr, uid, ids, context=context):
+ level = 0
+ if report.parent_id:
+ level = report.parent_id.level + 1
+ res[report.id] = level
+ return res
+
+ def _get_children_by_order(self, cr, uid, ids, context=None):
+ res = []
+ for id in ids:
+ res.append(id)
+ ids2 = self.search(cr, uid, [('parent_id', '=', id)], order='sequence ASC', context=context)
+ res += self._get_children_by_order(cr, uid, ids2, context=context)
+ return res
+
+ def _get_balance(self, cr, uid, ids, name, args, context=None):
+ res = {}
+ res_all = {}
+ for report in self.browse(cr, uid, ids, context=context):
+ balance = 0.0
+ if report.id in res_all:
+ balance = res_all[report.id]
+ elif report.type == 'accounts':
+ # it's the sum of balance of the linked accounts
+ for a in report.account_ids:
+ balance += a.balance
+ elif report.type == 'account_report' and report.account_report_id:
+ # it's the amount of the linked report
+ res2 = self._get_balance(cr, uid, [report.account_report_id.id], 'balance', False, context=context)
+ res_all.update(res2)
+ for key, value in res2.items():
+ balance += value
+ elif report.type == 'sum':
+ # it's the sum of balance of the children of this account.report
+ #for child in report.children_ids:
+ res2 = self._get_balance(cr, uid, [rec.id for rec in report.children_ids], 'balance', False, context=context)
+ res_all.update(res2)
+ for key, value in res2.items():
+ balance += value
+ res[report.id] = balance
+ res_all[report.id] = balance
+ return res
+
+ _columns = {
+ 'name': fields.char('Report Name', size=128, required=True),
+ 'parent_id': fields.many2one('account.financial.report', 'Parent'),
+ 'children_ids': fields.one2many('account.financial.report', 'parent_id', 'Account Report'),
+ 'sequence': fields.integer('Sequence'),
+ 'note': fields.text('Notes'),
+ 'balance': fields.function(_get_balance, 'Balance'),
+ 'level': fields.function(_get_level, string='Level', store=True, type='integer'),
+ 'type': fields.selection([
+ ('sum','View'),
+ ('accounts','Accounts'),
+ ('account_type','Account Type'),
+ ('account_report','Report Value'),
+ ],'Type'),
+ 'account_ids': fields.many2many('account.account', 'account_account_financial_report', 'report_line_id', 'account_id', 'Accounts'),
+ 'display_detail': fields.boolean('Display details', help='Display every account with its balance instead of the sum.'),
+ 'account_report_id': fields.many2one('account.financial.report', 'Report Value'),
+ 'account_type_ids': fields.many2many('account.account.type', 'account_account_financial_report_type', 'report_id', 'account_type_id', 'Account Types'),
+ }
+
+ _defaults = {
+ 'type': 'sum',
+ }
+
+account_financial_report()
+
+# ---------------------------------------------------------
+# Account generation from template wizards
+# ---------------------------------------------------------
class wizard_multi_charts_accounts(osv.osv_memory):
"""
@@ -2706,7 +2811,7 @@ class wizard_multi_charts_accounts(osv.osv_memory):
unconfigured_cmp = list(set(company_ids)-set(configured_cmp))
for field in res['fields']:
if field == 'company_id':
- res['fields'][field]['domain'] = unconfigured_cmp
+ res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
res['fields'][field]['selection'] = [('', '')]
if unconfigured_cmp:
cmp_select = [(line.id, line.name) for line in self.pool.get('res.company').browse(cr, uid, unconfigured_cmp)]
diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py
index f712cc9ab0c..277e2aa04b1 100644
--- a/addons/account/account_bank_statement.py
+++ b/addons/account/account_bank_statement.py
@@ -58,13 +58,6 @@ class account_bank_statement(osv.osv):
journal_id = ids[0]
return journal_id
- def _default_balance_start(self, cr, uid, context=None):
- cr.execute('select id from account_bank_statement where journal_id=%s order by date desc limit 1', (1,))
- res = cr.fetchone()
- if res:
- return self.browse(cr, uid, res[0], context=context).balance_end
- return 0.0
-
def _end_balance(self, cursor, user, ids, name, attr, context=None):
res_currency_obj = self.pool.get('res.currency')
res_users_obj = self.pool.get('res.users')
@@ -90,7 +83,8 @@ class account_bank_statement(osv.osv):
res[statement.id] -= res_currency_obj.compute(cursor,
user, company_currency_id, currency_id,
line.credit, context=context)
- if statement.state == 'draft':
+
+ if statement.state in ('draft', 'open'):
for line in statement.line_ids:
res[statement.id] += line.amount
for r in res:
@@ -127,7 +121,7 @@ class account_bank_statement(osv.osv):
_name = "account.bank.statement"
_description = "Bank Statement"
_columns = {
- 'name': fields.char('Name', size=64, required=True, help='if you give the Name other then /, its created Accounting Entries Move will be with same name as statement name. This allows the statement entries to have the same references than the statement itself', states={'confirm': [('readonly', True)]}),
+ 'name': fields.char('Name', size=64, required=True, states={'draft': [('readonly', False)]}, readonly=True, help='if you give the Name other then /, its created Accounting Entries Move will be with same name as statement name. This allows the statement entries to have the same references than the statement itself'), # readonly for account_cash_statement
'date': fields.date('Date', required=True, states={'confirm': [('readonly', True)]}),
'journal_id': fields.many2one('account.journal', 'Journal', required=True,
readonly=True, states={'draft':[('readonly',False)]}),
@@ -136,19 +130,21 @@ class account_bank_statement(osv.osv):
'balance_start': fields.float('Starting Balance', digits_compute=dp.get_precision('Account'),
states={'confirm':[('readonly',True)]}),
'balance_end_real': fields.float('Ending Balance', digits_compute=dp.get_precision('Account'),
- states={'confirm':[('readonly', True)]}),
- 'balance_end': fields.function(_end_balance, string='Balance'),
+ states={'confirm': [('readonly', True)]}),
+ 'balance_end': fields.function(_end_balance, store=True, # store=True for account_cash_statement
+ string="Balance", help='Balance as calculated based on Starting Balance and transaction lines'),
'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
'line_ids': fields.one2many('account.bank.statement.line',
'statement_id', 'Statement lines',
states={'confirm':[('readonly', True)]}),
'move_line_ids': fields.one2many('account.move.line', 'statement_id',
'Entry lines', states={'confirm':[('readonly',True)]}),
- 'state': fields.selection([('draft', 'Draft'),('confirm', 'Confirmed')],
- 'State', required=True,
- states={'confirm': [('readonly', True)]}, readonly="1",
- help='When new statement is created the state will be \'Draft\'. \
- \n* And after getting confirmation from the bank it will be in \'Confirmed\' state.'),
+ 'state': fields.selection([('draft', 'Draft'),
+ ('open','Open'), # used by cash statements
+ ('confirm', 'Closed')],
+ 'State', required=True, readonly="1",
+ help='When new statement is created the state will be \'Draft\'.\n'
+ 'And after getting confirmation from the bank it will be in \'Confirmed\' state.'),
'currency': fields.function(_currency, string='Currency',
type='many2one', relation='res.currency'),
'account_id': fields.related('journal_id', 'default_debit_account_id', type='many2one', relation='account.account', string='Account used in this journal', readonly=True, help='used in statement reconciliation domain, but shouldn\'t be used elswhere.'),
@@ -158,7 +154,6 @@ class account_bank_statement(osv.osv):
'name': "/",
'date': lambda *a: time.strftime('%Y-%m-%d'),
'state': 'draft',
- 'balance_start': _default_balance_start,
'journal_id': _default_journal_id,
'period_id': _get_period,
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.bank.statement',context=c),
@@ -386,7 +381,7 @@ class account_bank_statement(osv.osv):
if t['state'] in ('draft'):
unlink_ids.append(t['id'])
else:
- raise osv.except_osv(_('Invalid action !'), _('Cannot delete bank statement(s) which are already confirmed !'))
+ raise osv.except_osv(_('Invalid action !'), _('In order to delete a bank statement, you must first cancel it to delete related journal items.'))
osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
return True
diff --git a/addons/account/account_cash_statement.py b/addons/account/account_cash_statement.py
index 982383ea2a7..3957e75c860 100644
--- a/addons/account/account_cash_statement.py
+++ b/addons/account/account_cash_statement.py
@@ -119,39 +119,6 @@ class account_cash_statement(osv.osv):
res2[statement.id] = encoding_total
return res2
- def _end_balance(self, cursor, user, ids, name, attr, context=None):
- res_currency_obj = self.pool.get('res.currency')
- res_users_obj = self.pool.get('res.users')
- res = {}
-
- company_currency_id = res_users_obj.browse(cursor, user, user,
- context=context).company_id.currency_id.id
-
- statements = self.browse(cursor, user, ids, context=context)
- for statement in statements:
- res[statement.id] = statement.balance_start
- currency_id = statement.currency.id
- for line in statement.move_line_ids:
- if line.debit > 0:
- if line.account_id.id == \
- statement.journal_id.default_debit_account_id.id:
- res[statement.id] += res_currency_obj.compute(cursor,
- user, company_currency_id, currency_id,
- line.debit, context=context)
- else:
- if line.account_id.id == \
- statement.journal_id.default_credit_account_id.id:
- res[statement.id] -= res_currency_obj.compute(cursor,
- user, company_currency_id, currency_id,
- line.credit, context=context)
-
- if statement.state in ('draft', 'open'):
- for line in statement.line_ids:
- res[statement.id] += line.amount
- for r in res:
- res[r] = round(res[r], 2)
- return res
-
def _get_company(self, cr, uid, context=None):
user_pool = self.pool.get('res.users')
company_pool = self.pool.get('res.company')
@@ -218,18 +185,11 @@ class account_cash_statement(osv.osv):
return res
_columns = {
- 'balance_end_real': fields.float('Closing Balance', digits_compute=dp.get_precision('Account'), states={'confirm': [('readonly', True)]}, help="closing balance entered by the cashbox verifier"),
- 'state': fields.selection(
- [('draft', 'Draft'),
- ('confirm', 'Closed'),
- ('open','Open')], 'State', required=True, states={'confirm': [('readonly', True)]}, readonly="1"),
'total_entry_encoding': fields.function(_get_sum_entry_encoding, store=True, string="Cash Transaction", help="Total cash transactions"),
'closing_date': fields.datetime("Closed On"),
- 'balance_end': fields.function(_end_balance, store=True, string='Balance', help="Closing balance based on Starting Balance and Cash Transactions"),
'balance_end_cash': fields.function(_balance_end_cash, store=True, string='Balance', help="Closing balance based on cashBox"),
'starting_details_ids': fields.one2many('account.cashbox.line', 'starting_id', string='Opening Cashbox'),
'ending_details_ids': fields.one2many('account.cashbox.line', 'ending_id', string='Closing Cashbox'),
- 'name': fields.char('Name', size=64, required=True, states={'draft': [('readonly', False)]}, readonly=True, help='if you give the Name other then /, its created Accounting Entries Move will be with same name as statement name. This allows the statement entries to have the same references than the statement itself'),
'user_id': fields.many2one('res.users', 'Responsible', required=False),
}
_defaults = {
@@ -247,7 +207,7 @@ class account_cash_statement(osv.osv):
]
open_jrnl = self.search(cr, uid, sql)
if open_jrnl:
- raise osv.except_osv(_('Error'), _('You can not have two open register for the same journal'))
+ raise osv.except_osv(_('Error'), _('You can not have two open register for the same journal!'))
if self.pool.get('account.journal').browse(cr, uid, vals['journal_id'], context=context).type == 'cash':
open_close = self._get_cash_open_close_box_lines(cr, uid, context)
diff --git a/addons/account/account_installer.xml b/addons/account/account_installer.xml
index 151f7bbc0f0..374304135df 100644
--- a/addons/account/account_installer.xml
+++ b/addons/account/account_installer.xml
@@ -23,7 +23,7 @@
23
-
+
diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml
index 545bfd41f10..97852a11b5f 100644
--- a/addons/account/account_invoice_view.xml
+++ b/addons/account/account_invoice_view.xml
@@ -294,7 +294,7 @@
-
+
@@ -355,7 +355,7 @@
search
-
+
@@ -364,16 +364,16 @@
-
-
+
+
-
-
-
+
+
+
diff --git a/addons/account/account_menuitem.xml b/addons/account/account_menuitem.xml
index 062e1f6382b..89538603ae7 100644
--- a/addons/account/account_menuitem.xml
+++ b/addons/account/account_menuitem.xml
@@ -30,6 +30,7 @@
+
diff --git a/addons/account/report/account_tax_code.rml b/addons/account/report/account_tax_code.rml
index 7489ac72a39..10a7c76a821 100644
--- a/addons/account/report/account_tax_code.rml
+++ b/addons/account/report/account_tax_code.rml
@@ -129,10 +129,10 @@
[[ line['name'] ]]
@@ -141,4 +141,4 @@
-
\ No newline at end of file
+
diff --git a/addons/account/report/account_tax_report.rml b/addons/account/report/account_tax_report.rml
index de4aa415fef..f489ee1143e 100644
--- a/addons/account/report/account_tax_report.rml
+++ b/addons/account/report/account_tax_report.rml
@@ -153,7 +153,7 @@
[[ repeatIn(get_lines(data['form']['based_on'], data['form']['company_id']), 'o') ]][[ (o['level']) ]] [[ (len(o['level'])<5 and setTag('para','para',{'fontName':'Helvetica-Bold'})) or removeParentNode('font') ]][[ o['code'] ]] [[ o['name'] ]]
[[ len(o['level'])<5 and setTag('para','para',{'fontName':"Helvetica-Bold"}) or removeParentNode('font')]][[ o['type']=='view' and removeParentNode('font') ]][[ formatLang(o['debit']) ]][[ o['type']<>'view' and removeParentNode('font') ]][[ formatLang(o['debit']) ]]
[[ len(o['level'])<5 and setTag('para','para',{'fontName':"Helvetica-Bold"}) or removeParentNode('font')]][[ o['type']=='view' and removeParentNode('font') ]][[ formatLang(o['credit']) ]][[ o['type']<>'view' and removeParentNode('font') ]][[ formatLang(o['credit'])]]
-
[[ len(o['level'])<5 and setTag('para','para',{'fontName':"Helvetica-Bold"}) or removeParentNode('font')]][[ o['type']=='view' and removeParentNode('font') ]][[ formatLang(o['tax_amount']) ]] [[ company.currency_id.symbol ]][[ o['type']<>'view' and removeParentNode('font') ]][[ formatLang(o['tax_amount']) ]] [[ company.currency_id.symbol ]]
+
[[ len(o['level'])<5 and setTag('para','para',{'fontName':"Helvetica-Bold"}) or removeParentNode('font')]][[ o['type']=='view' and removeParentNode('font') ]][[ formatLang(o['tax_amount'], currency_obj=company.currency_id) ]][[ o['type']<>'view' and removeParentNode('font') ]][[ formatLang(o['tax_amount'], currency_obj=company.currency_id) ]]
diff --git a/addons/account/report/common_report_header.py b/addons/account/report/common_report_header.py
index 48e26e839d3..5769248405f 100644
--- a/addons/account/report/common_report_header.py
+++ b/addons/account/report/common_report_header.py
@@ -94,10 +94,10 @@ class common_report_header(object):
def _get_filter(self, data):
if data.get('form', False) and data['form'].get('filter', False):
if data['form']['filter'] == 'filter_date':
- return _('Date')
+ return 'Date'
elif data['form']['filter'] == 'filter_period':
- return _('Periods')
- return _('No Filter')
+ return 'Periods'
+ return 'No Filter'
def _sum_debit_period(self, period_id, journal_id=None):
journals = journal_id or self.journal_ids
diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv
index cd945a2248d..79e4dbb0c03 100644
--- a/addons/account/security/ir.model.access.csv
+++ b/addons/account/security/ir.model.access.csv
@@ -67,6 +67,7 @@
"access_account_fiscalyear_employee","account.fiscalyear employee","model_account_fiscalyear","base.group_user",1,0,0,0
"access_res_currency_account_manager","res.currency account manager","base.model_res_currency","group_account_manager",1,1,1,1
"access_res_currency_rate_account_manager","res.currency.rate account manager","base.model_res_currency_rate","group_account_manager",1,1,1,1
+"access_res_currency_rate_type_account_manager","res.currency.rate.type account manager","base.model_res_currency_rate_type","group_account_manager",1,1,1,1
"access_account_invoice_user","account.invoice user","model_account_invoice","base.group_user",1,0,0,0
"access_account_invoice_user","account.invoice.line user","model_account_invoice_line","base.group_user",1,0,0,0
"access_account_payment_term_partner_manager","account.payment.term partner manager","model_account_payment_term","base.group_user",1,0,0,0
@@ -127,4 +128,7 @@
"access_account_sequence_fiscal_year_sale_user","account.sequence.fiscalyear.sale.user","model_account_sequence_fiscalyear","base.group_sale_salesman",1,1,1,0
"access_account_sequence_fiscal_year_sale_manager","account.sequence.fiscalyear.sale.manager","model_account_sequence_fiscalyear","base.group_sale_manager",1,1,1,1
"access_account_treasury_report_manager","account.treasury.report.manager","model_account_treasury_report","account.group_account_manager",1,0,0,0
+"access_account_financial_report","account.financial.report","model_account_financial_report","account.group_account_user",1,1,1,1
+"access_account_financial_report_invoice","account.financial.report invoice","model_account_financial_report","account.group_account_invoice",1,0,0,0
+"access_account_financial_report_manager","account.financial.report","model_account_financial_report","account.group_account_manager",1,1,1,1
diff --git a/addons/account/wizard/__init__.py b/addons/account/wizard/__init__.py
index caed6fc3977..d82eff62142 100644
--- a/addons/account/wizard/__init__.py
+++ b/addons/account/wizard/__init__.py
@@ -47,6 +47,7 @@ import account_open_closed_fiscalyear
import account_invoice_state
import account_chart
import account_tax_chart
+import account_financial_report
#TODO: remove this file no moe used
# also remove related view fiel
diff --git a/addons/account/wizard/account_financial_report.py b/addons/account/wizard/account_financial_report.py
new file mode 100644
index 00000000000..de9cae3a00c
--- /dev/null
+++ b/addons/account/wizard/account_financial_report.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from osv import fields, osv
+
+class accounting_report(osv.osv_memory):
+ _name = "accounting.report"
+ _inherit = "account.common.report"
+ _description = "Accounting Report"
+
+ _columns = {
+ 'enable_filter': fields.boolean('Enable Comparison'),
+ 'account_report_id': fields.many2one('account.financial.report', 'Account Reports', required=True),
+ 'label_filter': fields.char('Column Label', size=32, help="This label will be displayed on report to show the balance computed for the given comparison filter."),
+ 'fiscalyear_id_cmp': fields.many2one('account.fiscalyear', 'Fiscal Year', help='Keep empty for all open fiscal year'),
+ 'filter_cmp': fields.selection([('filter_no', 'No Filters'), ('filter_date', 'Date'), ('filter_period', 'Periods')], "Filter by", required=True),
+ 'period_from_cmp': fields.many2one('account.period', 'Start Period'),
+ 'period_to_cmp': fields.many2one('account.period', 'End Period'),
+ 'date_from_cmp': fields.date("Start Date"),
+ 'date_to_cmp': fields.date("End Date"),
+ }
+
+ _defaults = {
+ 'filter_cmp': 'filter_no',
+ 'target_move': 'posted',
+ }
+
+ def _build_comparison_context(self, cr, uid, ids, data, context=None):
+ if context is None:
+ context = {}
+ result = {}
+ result['fiscalyear'] = 'fiscalyear_id_cmp' in data['form'] and data['form']['fiscalyear_id_cmp'] or False
+ result['journal_ids'] = 'journal_ids' in data['form'] and data['form']['journal_ids'] or False
+ result['chart_account_id'] = 'chart_account_id' in data['form'] and data['form']['chart_account_id'] or False
+ if data['form']['filter_cmp'] == 'filter_date':
+ result['date_from'] = data['form']['date_from_cmp']
+ result['date_to'] = data['form']['date_to_cmp']
+ elif data['form']['filter_cmp'] == 'filter_period':
+ if not data['form']['period_from_cmp'] or not data['form']['period_to_cmp']:
+ raise osv.except_osv(_('Error'),_('Select a starting and an ending period'))
+ result['period_from'] = data['form']['period_from_cmp']
+ result['period_to'] = data['form']['period_to_cmp']
+ return result
+
+ def check_report(self, cr, uid, ids, context=None):
+ if context is None:
+ context = {}
+ res = super(accounting_report, self).check_report(cr, uid, ids, context=context)
+ data = {}
+ data['form'] = self.read(cr, uid, ids, ['account_report_id', 'date_from_cmp', 'date_to_cmp', 'fiscalyear_id_cmp', 'journal_ids', 'period_from_cmp', 'period_to_cmp', 'filter_cmp', 'chart_account_id', 'target_move'], context=context)[0]
+ for field in ['fiscalyear_id_cmp', 'chart_account_id', 'period_from_cmp', 'period_to_cmp', 'account_report_id']:
+ if isinstance(data['form'][field], tuple):
+ data['form'][field] = data['form'][field][0]
+ comparison_context = self._build_comparison_context(cr, uid, ids, data, context=context)
+ res['datas']['form']['comparison_context'] = comparison_context
+ return res
+
+ def _print_report(self, cr, uid, ids, data, context=None):
+ data['form'].update(self.read(cr, uid, ids, ['date_from_cmp', 'date_to_cmp', 'fiscalyear_id_cmp', 'period_from_cmp', 'period_to_cmp', 'filter_cmp', 'account_report_id', 'enable_filter', 'label_filter'], context=context)[0])
+ return {
+ 'type': 'ir.actions.report.xml',
+ 'report_name': 'account.financial.report',
+ 'datas': data,
+ }
+
+accounting_report()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account/wizard/account_financial_report_view.xml b/addons/account/wizard/account_financial_report_view.xml
new file mode 100644
index 00000000000..1472da4ba87
--- /dev/null
+++ b/addons/account/wizard/account_financial_report_view.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+ Accounting Report
+ accounting.report
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Financial Reports
+ accounting.report
+ ir.actions.act_window
+ form
+ form
+
+ new
+
+
+
+
+
+
diff --git a/addons/account/wizard/account_invoice_refund.py b/addons/account/wizard/account_invoice_refund.py
index 74feaca27be..8de1dc79bfb 100644
--- a/addons/account/wizard/account_invoice_refund.py
+++ b/addons/account/wizard/account_invoice_refund.py
@@ -41,12 +41,16 @@ class account_invoice_refund(osv.osv_memory):
def _get_journal(self, cr, uid, context=None):
obj_journal = self.pool.get('account.journal')
+ user_obj = self.pool.get('res.users')
if context is None:
context = {}
- journal = obj_journal.search(cr, uid, [('type', '=', 'sale_refund')])
- if context.get('type', False):
- if context['type'] in ('in_invoice', 'in_refund'):
- journal = obj_journal.search(cr, uid, [('type', '=', 'purchase_refund')])
+ inv_type = context.get('type', 'out_invoice')
+ company_id = user_obj.browse(cr, uid, uid, context=context).company_id.id
+ type = (inv_type == 'out_invoice') and 'sale_refund' or \
+ (inv_type == 'out_refund') and 'sale' or \
+ (inv_type == 'in_invoice') and 'purchase_refund' or \
+ (inv_type == 'in_refund') and 'purchase'
+ journal = obj_journal.search(cr, uid, [('type', '=', type), ('company_id','=',company_id)], limit=1, context=context)
return journal and journal[0] or False
_defaults = {
@@ -57,15 +61,17 @@ class account_invoice_refund(osv.osv_memory):
def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
journal_obj = self.pool.get('account.journal')
+ user_obj = self.pool.get('res.users')
res = super(account_invoice_refund,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
- type = context.get('journal_type', 'sale_refund')
- if type in ('sale', 'sale_refund'):
- type = 'sale_refund'
- else:
- type = 'purchase_refund'
+ type = context.get('type', 'out_invoice')
+ company_id = user_obj.browse(cr, uid, uid, context=context).company_id.id
+ journal_type = (type == 'out_invoice') and 'sale_refund' or \
+ (type == 'out_refund') and 'sale' or \
+ (type == 'in_invoice') and 'purchase_refund' or \
+ (type == 'in_refund') and 'purchase'
for field in res['fields']:
if field == 'journal_id':
- journal_select = journal_obj._name_search(cr, uid, '', [('type', '=', type)], context=context, limit=None, name_get_uid=1)
+ journal_select = journal_obj._name_search(cr, uid, '', [('type', '=', journal_type), ('company_id','child_of',[company_id])], context=context)
res['fields'][field]['selection'] = journal_select
return res
@@ -198,10 +204,10 @@ class account_invoice_refund(osv.osv_memory):
if 'value' in data and data['value']:
inv_obj.write(cr, uid, [inv_id], data['value'])
created_inv.append(inv_id)
- if inv.type in ('out_invoice', 'out_refund'):
- xml_id = 'action_invoice_tree3'
- else:
- xml_id = 'action_invoice_tree4'
+ xml_id = (inv.type == 'out_refund') and 'action_invoice_tree1' or \
+ (inv.type == 'in_refund') and 'action_invoice_tree2' or \
+ (inv.type == 'out_invoice') and 'action_invoice_tree3' or \
+ (inv.type == 'in_invoice') and 'action_invoice_tree4'
result = mod_obj.get_object_reference(cr, uid, 'account', xml_id)
id = result and result[1] or False
result = act_obj.read(cr, uid, id, context=context)
diff --git a/addons/account/wizard/account_reconcile.py b/addons/account/wizard/account_reconcile.py
index 30bac2ae1fd..d03051b2ccb 100644
--- a/addons/account/wizard/account_reconcile.py
+++ b/addons/account/wizard/account_reconcile.py
@@ -110,7 +110,7 @@ class account_move_line_reconcile_writeoff(osv.osv_memory):
It opens the write off wizard form, in that user can define the journal, account, analytic account for reconcile
"""
_name = 'account.move.line.reconcile.writeoff'
- _description = 'Account move line reconcile'
+ _description = 'Account move line reconcile (writeoff)'
_columns = {
'journal_id': fields.many2one('account.journal','Write-Off Journal', required=True),
'writeoff_acc_id': fields.many2one('account.account','Write-Off account', required=True),
diff --git a/addons/account_analytic_analysis/account_analytic_analysis_menu.xml b/addons/account_analytic_analysis/account_analytic_analysis_menu.xml
index 4ee4693abcb..bfb7a892668 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis_menu.xml
+++ b/addons/account_analytic_analysis/account_analytic_analysis_menu.xml
@@ -11,7 +11,7 @@
[('invoice_id','=',False)]
-
+ Overpassed Accounts
@@ -20,6 +20,6 @@
tree,form,graph[('date','<=',time.strftime('%Y-%m-%d')),('state','=','open')]
-
+
diff --git a/addons/account_analytic_analysis/i18n/mk.po b/addons/account_analytic_analysis/i18n/mk.po
new file mode 100644
index 00000000000..81d5d115207
--- /dev/null
+++ b/addons/account_analytic_analysis/i18n/mk.po
@@ -0,0 +1,296 @@
+# Macedonian translation for openobject-addons
+# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2011-01-11 11:14+0000\n"
+"PO-Revision-Date: 2011-09-14 08:10+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Macedonian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2011-09-15 04:47+0000\n"
+"X-Generator: Launchpad (build 13921)\n"
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,hours_qtt_invoiced:0
+msgid ""
+"Number of hours that can be invoiced plus those that already have been "
+"invoiced."
+msgstr ""
+"Број на часови кои можат да бидат фактурирани, плус часови кои веќе се "
+"фактурирани."
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,remaining_ca:0
+msgid "Computed using the formula: Max Invoice Price - Invoiced Amount."
+msgstr ""
+"Пресметано според формулата: Максимална фактурирана цена - Фактурирана цена."
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,remaining_hours:0
+msgid "Computed using the formula: Maximum Quantity - Hours Tot."
+msgstr "Пресметано според формулата: Максимална количина - Вкупно часови"
+
+#. module: account_analytic_analysis
+#: code:addons/account_analytic_analysis/account_analytic_analysis.py:532
+#: code:addons/account_analytic_analysis/account_analytic_analysis.py:703
+#, python-format
+msgid "AccessError"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,last_invoice_date:0
+msgid "Date of the last invoice created for this analytic account."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: model:ir.module.module,description:account_analytic_analysis.module_meta_information
+msgid ""
+"\n"
+"This module is for modifying account analytic view to show\n"
+"important data to project manager of services companies.\n"
+"Adds menu to show relevant information to each manager..\n"
+"\n"
+"You can also view the report of account analytic summary\n"
+"user-wise as well as month wise.\n"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,last_invoice_date:0
+msgid "Last Invoice Date"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,theorical_margin:0
+msgid "Computed using the formula: Theorial Revenue - Total Costs"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,real_margin_rate:0
+msgid "Real Margin Rate (%)"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,ca_theorical:0
+msgid "Theoretical Revenue"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,last_worked_invoiced_date:0
+msgid ""
+"If invoice from the costs, this is the date of the latest work or cost that "
+"have been invoiced."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: model:ir.ui.menu,name:account_analytic_analysis.menu_invoicing
+msgid "Billing"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,last_worked_date:0
+msgid "Date of Last Cost/Work"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,total_cost:0
+msgid "Total Costs"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,hours_quantity:0
+msgid ""
+"Number of hours you spent on the analytic account (from timesheet). It "
+"computes on all journal of type 'general'."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,remaining_hours:0
+msgid "Remaining Hours"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,theorical_margin:0
+msgid "Theoretical Margin"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,ca_theorical:0
+msgid ""
+"Based on the costs you had on the project, what would have been the revenue "
+"if all these costs have been invoiced at the normal sale price provided by "
+"the pricelist."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,user_ids:0
+#: field:account_analytic_analysis.summary.user,user:0
+msgid "User"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,ca_to_invoice:0
+msgid "Uninvoiced Amount"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,real_margin:0
+msgid "Computed using the formula: Invoiced Amount - Total Costs."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,hours_qtt_non_invoiced:0
+msgid "Uninvoiced Hours"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,last_worked_date:0
+msgid "Date of the latest work done on this account."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: model:ir.module.module,shortdesc:account_analytic_analysis.module_meta_information
+msgid "report_account_analytic"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: model:ir.model,name:account_analytic_analysis.model_account_analytic_analysis_summary_user
+msgid "Hours Summary by User"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,ca_invoiced:0
+msgid "Invoiced Amount"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: code:addons/account_analytic_analysis/account_analytic_analysis.py:533
+#: code:addons/account_analytic_analysis/account_analytic_analysis.py:704
+#, python-format
+msgid "You try to bypass an access rule (Document type: %s)."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,last_worked_invoiced_date:0
+msgid "Date of Last Invoiced Cost"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,hours_qtt_invoiced:0
+msgid "Invoiced Hours"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,real_margin:0
+msgid "Real Margin"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: constraint:account.analytic.account:0
+msgid ""
+"Error! The currency has to be the same as the currency of the selected "
+"company"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,ca_invoiced:0
+msgid "Total customer invoiced amount for this account."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: model:ir.model,name:account_analytic_analysis.model_account_analytic_analysis_summary_month
+msgid "Hours summary by month"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,real_margin_rate:0
+msgid "Computes using the formula: (Real Margin / Total Costs) * 100."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,hours_qtt_non_invoiced:0
+msgid ""
+"Number of hours (from journal of type 'general') that can be invoiced if you "
+"invoice based on analytic account."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: view:account.analytic.account:0
+msgid "Analytic accounts"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,remaining_ca:0
+msgid "Remaining Revenue"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,ca_to_invoice:0
+msgid ""
+"If invoice from analytic account, the remaining amount you can invoice to "
+"the customer based on the total costs."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,revenue_per_hour:0
+msgid "Computed using the formula: Invoiced Amount / Hours Tot."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,revenue_per_hour:0
+msgid "Revenue per Hours (real)"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account_analytic_analysis.summary.month,unit_amount:0
+#: field:account_analytic_analysis.summary.user,unit_amount:0
+msgid "Total Time"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,month_ids:0
+#: field:account_analytic_analysis.summary.month,month:0
+msgid "Month"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account_analytic_analysis.summary.month,account_id:0
+#: field:account_analytic_analysis.summary.user,account_id:0
+#: model:ir.model,name:account_analytic_analysis.model_account_analytic_account
+msgid "Analytic Account"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: model:ir.actions.act_window,name:account_analytic_analysis.action_account_analytic_managed_overpassed
+#: model:ir.ui.menu,name:account_analytic_analysis.menu_action_account_analytic_managed_overpassed
+msgid "Overpassed Accounts"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: model:ir.actions.act_window,name:account_analytic_analysis.action_hr_tree_invoiced_all
+#: model:ir.ui.menu,name:account_analytic_analysis.menu_action_hr_tree_invoiced_all
+msgid "All Uninvoiced Entries"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: field:account.analytic.account,hours_quantity:0
+msgid "Hours Tot"
+msgstr ""
+
+#. module: account_analytic_analysis
+#: constraint:account.analytic.account:0
+msgid "Error! You can not create recursive analytic accounts."
+msgstr ""
+
+#. module: account_analytic_analysis
+#: help:account.analytic.account,total_cost:0
+msgid ""
+"Total of costs for this account. It includes real costs (from invoices) and "
+"indirect costs, like time spent on timesheets."
+msgstr ""
diff --git a/addons/account_analytic_default/__openerp__.py b/addons/account_analytic_default/__openerp__.py
index c2bea6783df..07cde53d003 100644
--- a/addons/account_analytic_default/__openerp__.py
+++ b/addons/account_analytic_default/__openerp__.py
@@ -39,7 +39,11 @@ Allows to automatically select analytic accounts based on criterions:
'images' : ['images/analytic_defaults.jpeg'],
'depends' : ['sale'],
'init_xml' : [],
- 'update_xml': ['security/ir.model.access.csv', 'account_analytic_default_view.xml'],
+ 'update_xml': [
+ 'security/ir.model.access.csv',
+ 'security/account_analytic_default_security.xml',
+ 'account_analytic_default_view.xml'
+ ],
'demo_xml' : [],
'installable': True,
'active': False,
diff --git a/addons/account_analytic_default/account_analytic_default_view.xml b/addons/account_analytic_default/account_analytic_default_view.xml
index f078ba09c22..753765019f6 100644
--- a/addons/account_analytic_default/account_analytic_default_view.xml
+++ b/addons/account_analytic_default/account_analytic_default_view.xml
@@ -44,7 +44,7 @@
search
-
+
diff --git a/addons/account_analytic_default/security/account_analytic_default_security.xml b/addons/account_analytic_default/security/account_analytic_default_security.xml
new file mode 100644
index 00000000000..7ff68c7d849
--- /dev/null
+++ b/addons/account_analytic_default/security/account_analytic_default_security.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Analytic Default multi company rule
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
+
+
diff --git a/addons/account_analytic_plans/report/crossovered_analytic.rml b/addons/account_analytic_plans/report/crossovered_analytic.rml
index 6e331ed1b83..f374aa5a2c2 100644
--- a/addons/account_analytic_plans/report/crossovered_analytic.rml
+++ b/addons/account_analytic_plans/report/crossovered_analytic.rml
@@ -240,7 +240,7 @@
[[ formatLang(a['ref_qty']) ]]
[[ formatLang(b['tot_perc'],digits=2) ]]%
diff --git a/addons/account_coda/account_coda_view.xml b/addons/account_coda/account_coda_view.xml
index a16e68d5ee0..c369cf7b921 100644
--- a/addons/account_coda/account_coda_view.xml
+++ b/addons/account_coda/account_coda_view.xml
@@ -45,7 +45,7 @@
search
-
+
diff --git a/addons/account_coda/i18n/sl.po b/addons/account_coda/i18n/sl.po
new file mode 100644
index 00000000000..b2c7e144795
--- /dev/null
+++ b/addons/account_coda/i18n/sl.po
@@ -0,0 +1,259 @@
+# Slovenian translation for openobject-addons
+# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2011-01-11 11:14+0000\n"
+"PO-Revision-Date: 2011-09-14 11:29+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Slovenian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2011-09-15 04:47+0000\n"
+"X-Generator: Launchpad (build 13921)\n"
+
+#. module: account_coda
+#: help:account.coda,journal_id:0
+#: field:account.coda.import,journal_id:0
+msgid "Bank Journal"
+msgstr "Bančni dnevnik"
+
+#. module: account_coda
+#: view:account.coda:0
+#: field:account.coda.import,note:0
+msgid "Log"
+msgstr "Dnevnik"
+
+#. module: account_coda
+#: model:ir.model,name:account_coda.model_account_coda_import
+msgid "Account Coda Import"
+msgstr ""
+
+#. module: account_coda
+#: field:account.coda,name:0
+msgid "Coda file"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda:0
+msgid "Group By..."
+msgstr "Združi po ..."
+
+#. module: account_coda
+#: field:account.coda.import,awaiting_account:0
+msgid "Default Account for Unrecognized Movement"
+msgstr ""
+
+#. module: account_coda
+#: help:account.coda,date:0
+msgid "Import Date"
+msgstr "Datum uvoza"
+
+#. module: account_coda
+#: field:account.coda,note:0
+msgid "Import log"
+msgstr "Dnevnik uvoza"
+
+#. module: account_coda
+#: view:account.coda.import:0
+msgid "Import"
+msgstr "Uvozi"
+
+#. module: account_coda
+#: view:account.coda:0
+msgid "Coda import"
+msgstr ""
+
+#. module: account_coda
+#: code:addons/account_coda/account_coda.py:51
+#, python-format
+msgid "Coda file not found for bank statement !!"
+msgstr ""
+
+#. module: account_coda
+#: help:account.coda.import,awaiting_account:0
+msgid ""
+"Set here the default account that will be used, if the partner is found but "
+"does not have the bank account, or if he is domiciled"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda:0
+#: field:account.coda,company_id:0
+msgid "Company"
+msgstr "Podjetje"
+
+#. module: account_coda
+#: help:account.coda.import,def_payable:0
+msgid ""
+"Set here the payable account that will be used, by default, if the partner "
+"is not found"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda:0
+msgid "Search Coda"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda:0
+#: field:account.coda,user_id:0
+msgid "User"
+msgstr "Uporabnik"
+
+#. module: account_coda
+#: view:account.coda:0
+#: field:account.coda,date:0
+msgid "Date"
+msgstr "Datum"
+
+#. module: account_coda
+#: model:ir.ui.menu,name:account_coda.menu_account_coda_statement
+msgid "Coda Import Logs"
+msgstr ""
+
+#. module: account_coda
+#: model:ir.model,name:account_coda.model_account_coda
+msgid "coda for an Account"
+msgstr ""
+
+#. module: account_coda
+#: field:account.coda.import,def_payable:0
+msgid "Default Payable Account"
+msgstr ""
+
+#. module: account_coda
+#: help:account.coda,name:0
+msgid "Store the detail of bank statements"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda.import:0
+msgid "Cancel"
+msgstr "Prekliči"
+
+#. module: account_coda
+#: view:account.coda.import:0
+msgid "Open Statements"
+msgstr ""
+
+#. module: account_coda
+#: code:addons/account_coda/wizard/account_coda_import.py:167
+#, python-format
+msgid "The bank account %s is not defined for the partner %s.\n"
+msgstr ""
+
+#. module: account_coda
+#: model:ir.ui.menu,name:account_coda.menu_account_coda_import
+msgid "Import Coda Statements"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda.import:0
+#: model:ir.actions.act_window,name:account_coda.action_account_coda_import
+msgid "Import Coda Statement"
+msgstr ""
+
+#. module: account_coda
+#: model:ir.module.module,description:account_coda.module_meta_information
+msgid ""
+"\n"
+" Module provides functionality to import\n"
+" bank statements from coda files.\n"
+" "
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda:0
+msgid "Statements"
+msgstr "Izpiski"
+
+#. module: account_coda
+#: field:account.bank.statement,coda_id:0
+msgid "Coda"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda.import:0
+msgid "Results :"
+msgstr "Rezultat"
+
+#. module: account_coda
+#: view:account.coda.import:0
+msgid "Result of Imported Coda Statements"
+msgstr ""
+
+#. module: account_coda
+#: help:account.coda.import,def_receivable:0
+msgid ""
+"Set here the receivable account that will be used, by default, if the "
+"partner is not found"
+msgstr ""
+
+#. module: account_coda
+#: field:account.coda.import,coda:0
+#: model:ir.actions.act_window,name:account_coda.act_account_payment_account_bank_statement
+msgid "Coda File"
+msgstr ""
+
+#. module: account_coda
+#: model:ir.model,name:account_coda.model_account_bank_statement
+msgid "Bank Statement"
+msgstr "Bančni izpisek"
+
+#. module: account_coda
+#: model:ir.actions.act_window,name:account_coda.action_account_coda
+msgid "Coda Logs"
+msgstr ""
+
+#. module: account_coda
+#: code:addons/account_coda/wizard/account_coda_import.py:311
+#, python-format
+msgid "Result"
+msgstr "Rezultat"
+
+#. module: account_coda
+#: view:account.coda.import:0
+msgid "Click on 'New' to select your file :"
+msgstr "Kliknite na \"Novo\" za izbiro vaše datoteke:"
+
+#. module: account_coda
+#: field:account.coda.import,def_receivable:0
+msgid "Default Receivable Account"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda.import:0
+msgid "Close"
+msgstr "Zapri"
+
+#. module: account_coda
+#: field:account.coda,statement_ids:0
+msgid "Generated Bank Statements"
+msgstr "Generirani bačni izpiski"
+
+#. module: account_coda
+#: model:ir.module.module,shortdesc:account_coda.module_meta_information
+msgid "Account CODA - import bank statements from coda file"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda.import:0
+msgid "Configure Your Journal and Account :"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda:0
+msgid "Coda Import"
+msgstr ""
+
+#. module: account_coda
+#: view:account.coda:0
+#: field:account.coda,journal_id:0
+msgid "Journal"
+msgstr "Dnevnik"
diff --git a/addons/account_followup/__openerp__.py b/addons/account_followup/__openerp__.py
index fd5cc859646..8de7468e43a 100644
--- a/addons/account_followup/__openerp__.py
+++ b/addons/account_followup/__openerp__.py
@@ -45,9 +45,10 @@ Note that if you want to check the followup level for a given partner/account en
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'images': ['images/follow_ups.jpeg','images/send_followups.jpeg'],
- 'depends': ['account'],
+ 'depends': ['account_accountant', 'mail'],
'init_xml': [],
'update_xml': [
+ 'security/account_followup_security.xml',
'security/ir.model.access.csv',
'wizard/account_followup_print_view.xml',
'report/account_followup_report.xml',
diff --git a/addons/account_followup/account_followup_demo.xml b/addons/account_followup/account_followup_demo.xml
index 2bf627de9b7..01b275de306 100644
--- a/addons/account_followup/account_followup_demo.xml
+++ b/addons/account_followup/account_followup_demo.xml
@@ -17,9 +17,9 @@
Dear %(partner_name)s,
-Exception made if there was a mistake of ours, it seems that the following amount staid unpaid. Please, take appropriate measures in order to carry out this payment in the next 8 days.
+Exception made if there was a mistake of ours, it seems that the following amount stays unpaid. Please, take appropriate measures in order to carry out this payment in the next 8 days.
-Would your payment have been carried out after this mail was sent, please consider the present one as void. Do not hesitate to contact our accounting department at (+32).10.68.94.39.
+Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to contact our accounting department at (+32).10.68.94.39.
Best Regards,
diff --git a/addons/account_followup/account_followup_view.xml b/addons/account_followup/account_followup_view.xml
index bd1d86ab7ef..a5f842b1be3 100644
--- a/addons/account_followup/account_followup_view.xml
+++ b/addons/account_followup/account_followup_view.xml
@@ -69,7 +69,7 @@
search
-
+
diff --git a/addons/account_followup/report/account_followup_report.xml b/addons/account_followup/report/account_followup_report.xml
index 55691cd9397..3f5d95a4fa6 100644
--- a/addons/account_followup/report/account_followup_report.xml
+++ b/addons/account_followup/report/account_followup_report.xml
@@ -41,7 +41,7 @@
search
-
+
+
+
+
+
+ Account Followup multi company rule
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
+
+ Account Followup Statistics by Partner Rule
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
+
+
diff --git a/addons/account_followup/wizard/account_followup_print.py b/addons/account_followup/wizard/account_followup_print.py
index 18b0fc1b874..78f1b8b34c7 100644
--- a/addons/account_followup/wizard/account_followup_print.py
+++ b/addons/account_followup/wizard/account_followup_print.py
@@ -205,6 +205,7 @@ class account_followup_print_all(osv.osv_memory):
move_obj = self.pool.get('account.move.line')
user_obj = self.pool.get('res.users')
line_obj = self.pool.get('account_followup.stat')
+ mail_message = self.pool.get('mail.message')
if context is None:
context = {}
@@ -277,7 +278,7 @@ class account_followup_print_all(osv.osv_memory):
msg = ''
if dest:
try:
- tools.email_send(src, dest, sub, body)
+ mail_message.schedule_with_attach(cr, uid, src, dest, sub, body, context=context)
msg_sent += partner.name + '\n'
except Exception, e:
raise osv.except_osv('Error !', e )
diff --git a/addons/account_payment/account_payment_view.xml b/addons/account_payment/account_payment_view.xml
index 7d6e281f58b..d0a543bf749 100644
--- a/addons/account_payment/account_payment_view.xml
+++ b/addons/account_payment/account_payment_view.xml
@@ -46,7 +46,7 @@
payment.modesearch
-
+
@@ -201,7 +201,7 @@
search
-
+
diff --git a/addons/account_sequence/account_sequence_installer_view.xml b/addons/account_sequence/account_sequence_installer_view.xml
index 540dc437b56..97158e50c12 100644
--- a/addons/account_sequence/account_sequence_installer_view.xml
+++ b/addons/account_sequence/account_sequence_installer_view.xml
@@ -20,7 +20,7 @@
You can enhance the Account Sequence Application by installing .
-
+
diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py
index da878458d6c..bdb498c8554 100644
--- a/addons/account_voucher/account_voucher.py
+++ b/addons/account_voucher/account_voucher.py
@@ -145,6 +145,16 @@ class account_voucher(osv.osv):
res['arch'] = etree.tostring(doc)
return res
+ def _check_paid(self, cr, uid, ids, name, args, context=None):
+ res = {}
+ for voucher in self.browse(cr, uid, ids, context=context):
+ ok = True
+ for line in voucher.move_ids:
+ if (line.account_id.type, 'in', ('receivable', 'payable')) and not line.reconcile_id:
+ ok = False
+ res[voucher.id] = ok
+ return res
+
def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, voucher_date, context=None):
if context is None:
context = {}
@@ -288,6 +298,7 @@ class account_voucher(osv.osv):
'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
'writeoff_amount': fields.function(_get_writeoff_amount, string='Write-Off Amount', type='float', readonly=True, multi="writeoff"),
'currency_rate_difference': fields.function(_get_writeoff_amount, string="Currency Rate Difference", type='float', multi="writeoff"),
+ 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
}
_defaults = {
'period_id': _get_period,
@@ -696,7 +707,7 @@ class account_voucher(osv.osv):
def unlink(self, cr, uid, ids, context=None):
for t in self.read(cr, uid, ids, ['state'], context=context):
if t['state'] not in ('draft', 'cancel'):
- raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
+ raise osv.except_osv(_('Invalid action !'), _('In order to delete a voucher, you must cancel it!'))
return super(account_voucher, self).unlink(cr, uid, ids, context=context)
def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
@@ -796,11 +807,11 @@ class account_voucher(osv.osv):
if voucher.currency_rate_difference > 0:
account_id = voucher.company_id.property_expense_currency_exchange
if not account_id:
- raise osv.except_osv(_('Warning'),_("Unable to create accounting entry for currency rate difference. You have to configure the field 'Income Currency Rate' on the company! "))
+ raise osv.except_osv(_('Warning'),_("Unable to create accounting entry for currency rate difference. You have to configure the field 'Expense Currency Rate' on the company! "))
else:
account_id = voucher.company_id.property_income_currency_exchange
if not account_id:
- raise osv.except_osv(_('Warning'),_("Unable to create accounting entry for currency rate difference. You have to configure the field 'Expense Currency Rate' on the company! "))
+ raise osv.except_osv(_('Warning'),_("Unable to create accounting entry for currency rate difference. You have to configure the field 'Income Currency Rate' on the company! "))
currency_diff_line = {
'name': _('Currency Difference'),
@@ -871,13 +882,22 @@ class account_voucher(osv.osv):
diff = currency_pool.compute(cr, uid, voucher_currency, company_currency, voucher.writeoff_amount, context=context_multi_currency)
account_id = False
write_off_name = ''
+
if voucher.payment_option == 'with_writeoff':
account_id = voucher.writeoff_acc_id.id
write_off_name = voucher.comment
elif voucher.type in ('sale', 'receipt'):
- account_id = voucher.partner_id.property_account_receivable.id
+ if not voucher.partner_id:
+ account_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('res_id','=',False)])
+ else:
+ account_id = voucher.partner_id.property_account_receivable.id
else:
- account_id = voucher.partner_id.property_account_payable.id
+ if not voucher.partner_id:
+ account_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('res_id','=',False)])
+ else:
+ account_id = voucher.partner_id.property_account_payable.id
+ if not account_id:
+ raise osv.except_osv(_('Error !'), _('No receivable/payable account defined in properties!'))
move_line = {
'name': write_off_name or name,
'account_id': account_id,
diff --git a/addons/account_voucher/account_voucher_view.xml b/addons/account_voucher/account_voucher_view.xml
index fd0e1a0c3c6..e44fd264879 100644
--- a/addons/account_voucher/account_voucher_view.xml
+++ b/addons/account_voucher/account_voucher_view.xml
@@ -119,7 +119,7 @@
1
-
+
@@ -130,12 +130,12 @@
-
+
-
+
diff --git a/addons/account_voucher/report/account_voucher_sales_receipt_view.xml b/addons/account_voucher/report/account_voucher_sales_receipt_view.xml
index 33e69b54c8a..70b3afbf8f1 100644
--- a/addons/account_voucher/report/account_voucher_sales_receipt_view.xml
+++ b/addons/account_voucher/report/account_voucher_sales_receipt_view.xml
@@ -48,7 +48,7 @@
search
-
+ search
-
+
@@ -19,12 +19,12 @@
-
+
-
+
@@ -41,7 +41,7 @@
search
-
+
@@ -52,12 +52,12 @@
-
+
-
+
diff --git a/addons/account_voucher/voucher_sales_purchase_view.xml b/addons/account_voucher/voucher_sales_purchase_view.xml
index 356c3f47ac9..7fe4acbf241 100644
--- a/addons/account_voucher/voucher_sales_purchase_view.xml
+++ b/addons/account_voucher/voucher_sales_purchase_view.xml
@@ -7,7 +7,7 @@
search
-
+
@@ -18,12 +18,12 @@
-
+
-
+
@@ -39,7 +39,7 @@
search
-
+
@@ -50,12 +50,12 @@
-
+
-
+
@@ -117,6 +117,7 @@
attrs="{'invisible':[('pay_now','!=','pay_now')]}"
/>
+
@@ -152,7 +153,7 @@
-
+
@@ -237,6 +238,7 @@
+
@@ -271,7 +273,7 @@
-
+
diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py
index a155352adf8..d60efd20144 100644
--- a/addons/analytic/analytic.py
+++ b/addons/analytic/analytic.py
@@ -30,22 +30,22 @@ class account_analytic_account(osv.osv):
_description = 'Analytic Account'
def _compute_level_tree(self, cr, uid, ids, child_ids, res, field_names, context=None):
- def recursive_computation(account_id, res):
- currency_obj = self.pool.get('res.currency')
- account = self.browse(cr, uid, account_id)
+ currency_obj = self.pool.get('res.currency')
+ recres = {}
+ def recursive_computation(account):
+ result2 = res[account.id].copy()
for son in account.child_ids:
- res = recursive_computation(son.id, res)
+ result = recursive_computation(son)
for field in field_names:
- if account.currency_id.id == son.currency_id.id or field=='quantity':
- res[account.id][field] += res[son.id][field]
- else:
- res[account.id][field] += currency_obj.compute(cr, uid, son.currency_id.id, account.currency_id.id, res[son.id][field], context=context)
- return res
+ if (account.currency_id.id != son.currency_id.id) and (field!='quantity'):
+ result[field] = currency_obj.compute(cr, uid, son.currency_id.id, account.currency_id.id, result[field], context=context)
+ result2[field] += result[field]
+ return result2
for account in self.browse(cr, uid, ids, context=context):
if account.id not in child_ids:
continue
- res = recursive_computation(account.id, res)
- return res
+ recres[account.id] = recursive_computation(account)
+ return recres
def _debit_credit_bal_qtty(self, cr, uid, ids, name, arg, context=None):
res = {}
diff --git a/addons/analytic/i18n/ar.po b/addons/analytic/i18n/ar.po
index ee7a5f9319e..036a3ef8c46 100644
--- a/addons/analytic/i18n/ar.po
+++ b/addons/analytic/i18n/ar.po
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
-"PO-Revision-Date: 2011-09-05 23:03+0000\n"
-"Last-Translator: Majed Majbour \n"
+"PO-Revision-Date: 2011-09-22 02:41+0000\n"
+"Last-Translator: kifcaliph \n"
"Language-Team: Arabic \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-09-07 04:35+0000\n"
-"X-Generator: Launchpad (build 13861)\n"
+"X-Launchpad-Export-Date: 2011-09-22 04:48+0000\n"
+"X-Generator: Launchpad (build 13996)\n"
#. module: analytic
#: field:account.analytic.account,child_ids:0
@@ -25,7 +25,7 @@ msgstr "حساب فرعي"
#. module: analytic
#: field:account.analytic.account,name:0
msgid "Account Name"
-msgstr ""
+msgstr "اسم الحساب"
#. module: analytic
#: help:account.analytic.line,unit_amount:0
@@ -44,12 +44,12 @@ msgstr ""
#. module: analytic
#: field:account.analytic.account,state:0
msgid "State"
-msgstr ""
+msgstr "المحافظة / الولاية"
#. module: analytic
#: field:account.analytic.account,user_id:0
msgid "Account Manager"
-msgstr ""
+msgstr "مدير المحاسبة"
#. module: analytic
#: selection:account.analytic.account,state:0
@@ -114,7 +114,7 @@ msgstr "وصف"
#. module: analytic
#: selection:account.analytic.account,type:0
msgid "Normal"
-msgstr ""
+msgstr "عادي"
#. module: analytic
#: field:account.analytic.account,company_id:0
@@ -185,7 +185,7 @@ msgstr "جهة الإتصال"
msgid ""
"Error! The currency has to be the same as the currency of the selected "
"company"
-msgstr ""
+msgstr "خطأ! العملة لابد و أن تكون مثل العملة للشركة المختارة"
#. module: analytic
#: selection:account.analytic.account,state:0
@@ -208,11 +208,13 @@ msgid ""
"If you select the View Type, it means you won't allow to create journal "
"entries using that account."
msgstr ""
+"اذا اخترت نوع العرض، فذلك يعني انك لن تسمح بإنشاء قيود اليومية بإستخدام ذلك "
+"الحساب."
#. module: analytic
#: field:account.analytic.account,date:0
msgid "Date End"
-msgstr ""
+msgstr "نهاية التاريخ"
#. module: analytic
#: field:account.analytic.account,code:0
diff --git a/addons/analytic/security/analytic_security.xml b/addons/analytic/security/analytic_security.xml
index dfa8c840bb6..165bb92f6b9 100644
--- a/addons/analytic/security/analytic_security.xml
+++ b/addons/analytic/security/analytic_security.xml
@@ -7,6 +7,14 @@
['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
+ Analytic line multi company rule
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
Useability / Analytic Accounting
diff --git a/addons/anonymization/anonymization.py b/addons/anonymization/anonymization.py
index c4a2d9f0488..5d1f8a76e1c 100644
--- a/addons/anonymization/anonymization.py
+++ b/addons/anonymization/anonymization.py
@@ -52,7 +52,7 @@ class ir_model_fields_anonymization(osv.osv):
}
_sql_constraints = [
- ('model_id_field_id_uniq', 'unique (model_name, field_name)', _("You cannot have two records having the same model and the same field")),
+ ('model_id_field_id_uniq', 'unique (model_name, field_name)', _("You cannot have two fields with the same name on the same object!")),
]
def _get_global_state(self, cr, uid, context=None):
diff --git a/addons/auction/auction_demo.xml b/addons/auction/auction_demo.xml
index 7f34eb6697c..e93cc3ea04d 100644
--- a/addons/auction/auction_demo.xml
+++ b/addons/auction/auction_demo.xml
@@ -39,7 +39,7 @@
Auction AdjudicationsA7x*
- receivable
+ other
@@ -48,7 +48,7 @@
Auction Adjudication ExpensesA6x*
- payable
+ other
@@ -56,7 +56,7 @@
Auction Buyer CostsA8x*
- payable
+ other
@@ -64,7 +64,7 @@
Auction Seller CostsA9x*
- receivable
+ other
@@ -251,7 +251,7 @@
- Frais de vente
+ Frais de vente00auction
diff --git a/addons/auction/auction_view.xml b/addons/auction/auction_view.xml
index bcfedacce77..abcf53490a5 100644
--- a/addons/auction/auction_view.xml
+++ b/addons/auction/auction_view.xml
@@ -218,7 +218,7 @@
-
+
-
+
-
+
-
+
@@ -70,7 +69,7 @@
-
+
diff --git a/addons/audittrail/audittrail.py b/addons/audittrail/audittrail.py
index bfa6bca8f34..4eded2f299d 100644
--- a/addons/audittrail/audittrail.py
+++ b/addons/audittrail/audittrail.py
@@ -58,7 +58,7 @@ class audittrail_rule(osv.osv):
}
_sql_constraints = [
- ('model_uniq', 'unique (object_id)', """There is a rule defined on this object\n You can not define other on the same!""")
+ ('model_uniq', 'unique (object_id)', """There is a rule defined on this object\n You cannot define another one the same object!""")
]
__functions = {}
diff --git a/addons/audittrail/audittrail_view.xml b/addons/audittrail/audittrail_view.xml
index 7dd30a063ce..debe4d54c1a 100644
--- a/addons/audittrail/audittrail_view.xml
+++ b/addons/audittrail/audittrail_view.xml
@@ -56,7 +56,7 @@
search
-
+
@@ -64,7 +64,7 @@
-
+
@@ -158,13 +158,13 @@
search
-
+
-
+
diff --git a/addons/base_action_rule/__openerp__.py b/addons/base_action_rule/__openerp__.py
index b6796f8958d..1170557efad 100644
--- a/addons/base_action_rule/__openerp__.py
+++ b/addons/base_action_rule/__openerp__.py
@@ -37,7 +37,7 @@ trigger an automatic reminder email.
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
- 'depends': ['base'],
+ 'depends': ['base', 'mail'],
'init_xml': [
'base_action_rule_data.xml'
],
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 2db23e75893..805c35e4462 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -24,7 +24,7 @@ from tools.translate import _
from datetime import datetime
from datetime import timedelta
from tools.safe_eval import safe_eval
-import pooler
+import pooler
import re
import time
import tools
@@ -39,7 +39,7 @@ class base_action_rule(osv.osv):
_name = 'base.action.rule'
_description = 'Action Rules'
-
+
def _state_get(self, cr, uid, context=None):
""" Get State
@param self: The object pointer
@@ -55,7 +55,7 @@ class base_action_rule(osv.osv):
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values """
return [('', '')]
-
+
def priority_get(self, cr, uid, context=None):
""" Get Priority
@param self: The object pointer
@@ -65,57 +65,57 @@ class base_action_rule(osv.osv):
return [('', '')]
_columns = {
- 'name': fields.char('Rule Name', size=64, required=True),
- 'model_id': fields.many2one('ir.model', 'Object', required=True),
- 'create_date': fields.datetime('Create Date', readonly=1),
+ 'name': fields.char('Rule Name', size=64, required=True),
+ 'model_id': fields.many2one('ir.model', 'Object', required=True),
+ 'create_date': fields.datetime('Create Date', readonly=1),
'active': fields.boolean('Active', help="If the active field is set to False,\
- it will allow you to hide the rule without removing it."),
+ it will allow you to hide the rule without removing it."),
'sequence': fields.integer('Sequence', help="Gives the sequence order \
-when displaying a list of rules."),
+when displaying a list of rules."),
'trg_date_type': fields.selection([
- ('none', 'None'),
- ('create', 'Creation Date'),
- ('action_last', 'Last Action Date'),
- ('date', 'Date'),
- ('deadline', 'Deadline'),
- ], 'Trigger Date', size=16),
+ ('none', 'None'),
+ ('create', 'Creation Date'),
+ ('action_last', 'Last Action Date'),
+ ('date', 'Date'),
+ ('deadline', 'Deadline'),
+ ], 'Trigger Date', size=16),
'trg_date_range': fields.integer('Delay after trigger date', \
help="Delay After Trigger Date,\
specifies you can put a negative number. If you need a delay before the \
-trigger date, like sending a reminder 15 minutes before a meeting."),
+trigger date, like sending a reminder 15 minutes before a meeting."),
'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), \
- ('day', 'Days'), ('month', 'Months')], 'Delay type'),
- 'trg_user_id': fields.many2one('res.users', 'Responsible'),
- 'trg_partner_id': fields.many2one('res.partner', 'Partner'),
- 'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'),
- 'trg_state_from': fields.selection(_state_get, 'State', size=16),
- 'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16),
+ ('day', 'Days'), ('month', 'Months')], 'Delay type'),
+ 'trg_user_id': fields.many2one('res.users', 'Responsible'),
+ 'trg_partner_id': fields.many2one('res.partner', 'Partner'),
+ 'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'),
+ 'trg_state_from': fields.selection(_state_get, 'State', size=16),
+ 'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16),
- 'act_method': fields.char('Call Object Method', size=64),
- 'act_user_id': fields.many2one('res.users', 'Set Responsible to'),
- 'act_state': fields.selection(_state_get, 'Set State to', size=16),
+ 'act_method': fields.char('Call Object Method', size=64),
+ 'act_user_id': fields.many2one('res.users', 'Set Responsible to'),
+ 'act_state': fields.selection(_state_get, 'Set State to', size=16),
'act_email_cc': fields.char('Add Watchers (Cc)', size=250, help="\
These people will receive a copy of the future communication between partner \
-and users by email"),
+and users by email"),
'act_remind_partner': fields.boolean('Remind Partner', help="Check \
-this if you want the rule to send a reminder by email to the partner."),
+this if you want the rule to send a reminder by email to the partner."),
'act_remind_user': fields.boolean('Remind Responsible', help="Check \
-this if you want the rule to send a reminder by email to the user."),
- 'act_reply_to': fields.char('Reply-To', size=64),
- 'act_remind_attach': fields.boolean('Remind with Attachment', help="Check this if you want that all documents attached to the object be attached to the reminder email sent."),
+this if you want the rule to send a reminder by email to the user."),
+ 'act_reply_to': fields.char('Reply-To', size=64),
+ 'act_remind_attach': fields.boolean('Remind with Attachment', help="Check this if you want that all documents attached to the object be attached to the reminder email sent."),
'act_mail_to_user': fields.boolean('Mail to Responsible', help="Check\
- this if you want the rule to send an email to the responsible person."),
- 'act_mail_to_watchers': fields.boolean('Mail to Watchers (CC)',
+ this if you want the rule to send an email to the responsible person."),
+ 'act_mail_to_watchers': fields.boolean('Mail to Watchers (CC)',
help="Check this if you want \
-the rule to mark CC(mail to any other person defined in actions)."),
+the rule to mark CC(mail to any other person defined in actions)."),
'act_mail_to_email': fields.char('Mail to these Emails', size=128, \
- help="Email-id of the persons whom mail is to be sent"),
- 'act_mail_body': fields.text('Mail body', help="Content of mail"),
+ help="Email-id of the persons whom mail is to be sent"),
+ 'act_mail_body': fields.text('Mail body', help="Content of mail"),
'regex_name': fields.char('Regex on Resource Name', size=128, help="Regular expression for matching name of the resource\
\ne.g.: 'urgent.*' will search for records having name starting with the string 'urgent'\
-\nNote: This is case sensitive search."),
- 'server_action_id': fields.many2one('ir.actions.server', 'Server Action', help="Describes the action name.\neg:on which object which action to be taken on basis of which condition"),
- 'filter_id':fields.many2one('ir.filters', 'Filter', required=False),
+\nNote: This is case sensitive search."),
+ 'server_action_id': fields.many2one('ir.actions.server', 'Server Action', help="Describes the action name.\neg:on which object which action to be taken on basis of which condition"),
+ 'filter_id':fields.many2one('ir.filters', 'Filter', required=False),
'act_email_from' : fields.char('Email From', size=64, required=False,
help="Use a python expression to specify the right field on which one than we will use for the 'From' field of the header"),
'act_email_to' : fields.char('Email To', size=64, required=False,
@@ -124,17 +124,17 @@ the rule to mark CC(mail to any other person defined in actions)."),
}
_defaults = {
- 'active': lambda *a: True,
- 'trg_date_type': lambda *a: 'none',
- 'trg_date_range_type': lambda *a: 'day',
- 'act_mail_to_user': lambda *a: 0,
- 'act_remind_partner': lambda *a: 0,
- 'act_remind_user': lambda *a: 0,
- 'act_mail_to_watchers': lambda *a: 0,
+ 'active': lambda *a: True,
+ 'trg_date_type': lambda *a: 'none',
+ 'trg_date_range_type': lambda *a: 'day',
+ 'act_mail_to_user': lambda *a: 0,
+ 'act_remind_partner': lambda *a: 0,
+ 'act_remind_user': lambda *a: 0,
+ 'act_mail_to_watchers': lambda *a: 0,
}
-
+
_order = 'sequence'
-
+
def onchange_model_id(self, cr, uid, ids, name):
#This is not a good solution as it will affect the domain only on onchange
res = {'domain':{'filter_id':[]}}
@@ -174,7 +174,7 @@ the rule to mark CC(mail to any other person defined in actions)."),
self.pre_action(cr, uid, [new_id], model, context=context)
return new_id
return make_call_old
-
+
def _write(self, old_write, model, context=None):
if context is None:
context = {}
@@ -202,9 +202,9 @@ the rule to mark CC(mail to any other person defined in actions)."),
return True
def create(self, cr, uid, vals, context=None):
res_id = super(base_action_rule, self).create(cr, uid, vals, context=context)
- self._register_hook(cr, uid, [res_id], context=context)
+ self._register_hook(cr, uid, [res_id], context=context)
return res_id
-
+
def write(self, cr, uid, ids, vals, context=None):
res = super(base_action_rule, self).write(cr, uid, ids, vals, context=context)
self._register_hook(cr, uid, ids, context=context)
@@ -268,22 +268,19 @@ the rule to mark CC(mail to any other person defined in actions)."),
return body and tools.ustr(body) or ''
def format_mail(self, obj, body):
- """ Foramat Mail
- @param self: The object pointer """
-
data = {
- 'object_id': obj.id,
- 'object_subject': hasattr(obj, 'name') and obj.name or False,
- 'object_date': hasattr(obj, 'date') and obj.date or False,
- 'object_description': hasattr(obj, 'description') and obj.description or False,
- 'object_user': hasattr(obj, 'user_id') and (obj.user_id and obj.user_id.name) or '/',
+ 'object_id': obj.id,
+ 'object_subject': hasattr(obj, 'name') and obj.name or False,
+ 'object_date': hasattr(obj, 'date') and obj.date or False,
+ 'object_description': hasattr(obj, 'description') and obj.description or False,
+ 'object_user': hasattr(obj, 'user_id') and (obj.user_id and obj.user_id.name) or '/',
'object_user_email': hasattr(obj, 'user_id') and (obj.user_id and \
obj.user_id.user_email) or '/',
'object_user_phone': hasattr(obj, 'partner_address_id') and (obj.partner_address_id and \
obj.partner_address_id.phone) or '/',
- 'partner': hasattr(obj, 'partner_id') and (obj.partner_id and obj.partner_id.name) or '/',
+ 'partner': hasattr(obj, 'partner_id') and (obj.partner_id and obj.partner_id.name) or '/',
'partner_email': hasattr(obj, 'partner_address_id') and (obj.partner_address_id and\
- obj.partner_address_id.email) or '/',
+ obj.partner_address_id.email) or '/',
}
return self.format_body(body % data)
@@ -302,6 +299,7 @@ the rule to mark CC(mail to any other person defined in actions)."),
if context is None:
context = {}
+ mail_message = self.pool.get('mail.message')
body = self.format_mail(obj, body)
if not emailfrom:
if hasattr(obj, 'user_id') and obj.user_id and obj.user_id.user_email:
@@ -311,9 +309,9 @@ the rule to mark CC(mail to any other person defined in actions)."),
emailfrom = tools.ustr(emailfrom)
reply_to = emailfrom
if not emailfrom:
- raise osv.except_osv(_('Error!'),
+ raise osv.except_osv(_('Error!'),
_("No E-Mail ID Found for your Company address!"))
- return tools.email_send(emailfrom, emails, name, body, reply_to=reply_to, openobject_id=str(obj.id))
+ return mail_message.schedule_with_attach(cr, uid, emailfrom, emails, name, body, model='base.action.rule', reply_to=reply_to, res_id=obj.id)
def do_check(self, cr, uid, action, obj, context=None):
@@ -324,7 +322,7 @@ the rule to mark CC(mail to any other person defined in actions)."),
@param context: A standard dictionary for contextual values """
if context is None:
context = {}
- ok = True
+ ok = True
if action.filter_id:
if action.model_id.model == action.filter_id.model_id:
context.update(eval(action.filter_id.context))
@@ -479,7 +477,7 @@ the rule to mark CC(mail to any other person defined in actions)."),
return True
_constraints = [
- (_check_mail, 'Error: The mail is not well formated', ['act_mail_body']),
+ (_check_mail, 'Error: The mail is not well formated', ['act_mail_body']),
]
base_action_rule()
diff --git a/addons/base_action_rule/i18n/sl.po b/addons/base_action_rule/i18n/sl.po
new file mode 100644
index 00000000000..a61482c4783
--- /dev/null
+++ b/addons/base_action_rule/i18n/sl.po
@@ -0,0 +1,505 @@
+# Slovenian translation for openobject-addons
+# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2011-01-11 11:14+0000\n"
+"PO-Revision-Date: 2011-09-15 06:14+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Slovenian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2011-09-16 04:36+0000\n"
+"X-Generator: Launchpad (build 13955)\n"
+
+#. module: base_action_rule
+#: help:base.action.rule,act_mail_to_user:0
+msgid ""
+"Check this if you want the rule to send an email to the responsible person."
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,act_remind_partner:0
+msgid "Remind Partner"
+msgstr "Opomni partnerja"
+
+#. module: base_action_rule
+#: field:base.action.rule,trg_partner_categ_id:0
+msgid "Partner Category"
+msgstr "Kategorija partnerja"
+
+#. module: base_action_rule
+#: field:base.action.rule,act_mail_to_watchers:0
+msgid "Mail to Watchers (CC)"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,trg_state_to:0
+msgid "Button Pressed"
+msgstr "Pritisnjeni gumb"
+
+#. module: base_action_rule
+#: field:base.action.rule,model_id:0
+msgid "Object"
+msgstr "Predmet"
+
+#. module: base_action_rule
+#: field:base.action.rule,act_mail_to_email:0
+msgid "Mail to these Emails"
+msgstr "Sporočila na te e-pošte"
+
+#. module: base_action_rule
+#: field:base.action.rule,act_state:0
+msgid "Set State to"
+msgstr "Nastavi stanje na"
+
+#. module: base_action_rule
+#: field:base.action.rule,act_email_from:0
+msgid "Email From"
+msgstr "E-pošta od"
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Email Body"
+msgstr "Vsebina e-pošte"
+
+#. module: base_action_rule
+#: selection:base.action.rule,trg_date_range_type:0
+msgid "Days"
+msgstr "Dnevi"
+
+#. module: base_action_rule
+#: field:base.action.rule,last_run:0
+msgid "Last Run"
+msgstr "Zadnji zagon"
+
+#. module: base_action_rule
+#: code:addons/base_action_rule/base_action_rule.py:313
+#, python-format
+msgid "Error!"
+msgstr "Napaka!"
+
+#. module: base_action_rule
+#: field:base.action.rule,act_reply_to:0
+msgid "Reply-To"
+msgstr "Odgovori-na"
+
+#. module: base_action_rule
+#: help:base.action.rule,act_email_cc:0
+msgid ""
+"These people will receive a copy of the future communication between partner "
+"and users by email"
+msgstr ""
+"Ti ljudje bodo prejeli kopijo bodočih komunikacij med partnerjem in "
+"uporabniki po e-pošti"
+
+#. module: base_action_rule
+#: selection:base.action.rule,trg_date_range_type:0
+msgid "Minutes"
+msgstr "Minute"
+
+#. module: base_action_rule
+#: field:base.action.rule,name:0
+msgid "Rule Name"
+msgstr "Ime pravila"
+
+#. module: base_action_rule
+#: help:base.action.rule,act_remind_partner:0
+msgid ""
+"Check this if you want the rule to send a reminder by email to the partner."
+msgstr ""
+"Označite to, če želite da pravilo pošlje opomnik po e-pošti partnerju."
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Conditions on Model Partner"
+msgstr ""
+
+#. module: base_action_rule
+#: selection:base.action.rule,trg_date_type:0
+msgid "Deadline"
+msgstr "Rok"
+
+#. module: base_action_rule
+#: field:base.action.rule,trg_partner_id:0
+msgid "Partner"
+msgstr "Stranka"
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "%(object_subject)s = Object subject"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Email Reminders"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Special Keywords to Be Used in The Body"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,trg_state_from:0
+msgid "State"
+msgstr ""
+
+#. module: base_action_rule
+#: model:ir.actions.act_window,help:base_action_rule.base_action_rule_act
+msgid ""
+"Use automated actions to automatically trigger actions for various screens. "
+"Example: a lead created by a specific user may be automatically set to a "
+"specific sales team, or an opportunity which still has status pending after "
+"14 days might trigger an automatic reminder email."
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,act_mail_to_email:0
+msgid "Email-id of the persons whom mail is to be sent"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+#: model:ir.module.module,shortdesc:base_action_rule.module_meta_information
+msgid "Action Rule"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Fields to Change"
+msgstr ""
+
+#. module: base_action_rule
+#: selection:base.action.rule,trg_date_type:0
+msgid "Creation Date"
+msgstr "Ustvarjeno dne"
+
+#. module: base_action_rule
+#: selection:base.action.rule,trg_date_type:0
+msgid "Last Action Date"
+msgstr ""
+
+#. module: base_action_rule
+#: selection:base.action.rule,trg_date_range_type:0
+msgid "Hours"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "%(object_id)s = Object ID"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Delay After Trigger Date"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,act_remind_attach:0
+msgid "Remind with Attachment"
+msgstr ""
+
+#. module: base_action_rule
+#: constraint:ir.cron:0
+msgid "Invalid arguments"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,act_user_id:0
+msgid "Set Responsible to"
+msgstr ""
+
+#. module: base_action_rule
+#: selection:base.action.rule,trg_date_type:0
+msgid "None"
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,act_email_to:0
+msgid ""
+"Use a python expression to specify the right field on which one than we will "
+"use for the 'To' field of the header"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "%(object_user_phone)s = Responsible phone"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid ""
+"The rule uses the AND operator. The model must match all non-empty fields so "
+"that the rule executes the action described in the 'Actions' tab."
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,trg_date_range_type:0
+msgid "Delay type"
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,regex_name:0
+msgid ""
+"Regular expression for matching name of the resource\n"
+"e.g.: 'urgent.*' will search for records having name starting with the "
+"string 'urgent'\n"
+"Note: This is case sensitive search."
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,act_method:0
+msgid "Call Object Method"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,act_email_to:0
+msgid "Email To"
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,act_mail_to_watchers:0
+msgid ""
+"Check this if you want the rule to mark CC(mail to any other person defined "
+"in actions)."
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "%(partner)s = Partner name"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Note"
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,act_email_from:0
+msgid ""
+"Use a python expression to specify the right field on which one than we will "
+"use for the 'From' field of the header"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,trg_date_range:0
+msgid "Delay after trigger date"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Conditions"
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,trg_date_range:0
+msgid ""
+"Delay After Trigger Date,specifies you can put a negative number. If you "
+"need a delay before the trigger date, like sending a reminder 15 minutes "
+"before a meeting."
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,active:0
+msgid "Active"
+msgstr ""
+
+#. module: base_action_rule
+#: code:addons/base_action_rule/base_action_rule.py:314
+#, python-format
+msgid "No E-Mail ID Found for your Company address!"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,act_remind_user:0
+msgid "Remind Responsible"
+msgstr ""
+
+#. module: base_action_rule
+#: model:ir.module.module,description:base_action_rule.module_meta_information
+msgid "This module allows to implement action rules for any object."
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,sequence:0
+msgid "Gives the sequence order when displaying a list of rules."
+msgstr ""
+
+#. module: base_action_rule
+#: selection:base.action.rule,trg_date_range_type:0
+msgid "Months"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,filter_id:0
+msgid "Filter"
+msgstr ""
+
+#. module: base_action_rule
+#: selection:base.action.rule,trg_date_type:0
+msgid "Date"
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,server_action_id:0
+msgid ""
+"Describes the action name.\n"
+"eg:on which object which action to be taken on basis of which condition"
+msgstr ""
+
+#. module: base_action_rule
+#: model:ir.model,name:base_action_rule.model_ir_cron
+msgid "ir.cron"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "%(object_description)s = Object description"
+msgstr ""
+
+#. module: base_action_rule
+#: constraint:base.action.rule:0
+msgid "Error: The mail is not well formated"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Email Actions"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Email Information"
+msgstr ""
+
+#. module: base_action_rule
+#: model:ir.model,name:base_action_rule.model_base_action_rule
+msgid "Action Rules"
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,act_mail_body:0
+msgid "Content of mail"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,trg_user_id:0
+msgid "Responsible"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "%(partner_email)s = Partner Email"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "%(object_date)s = Creation date"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "%(object_user_email)s = Responsible Email"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,act_mail_body:0
+msgid "Mail body"
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,act_remind_user:0
+msgid ""
+"Check this if you want the rule to send a reminder by email to the user."
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Server Action to be Triggered"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,act_mail_to_user:0
+msgid "Mail to Responsible"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,act_email_cc:0
+msgid "Add Watchers (Cc)"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Conditions on Model Fields"
+msgstr ""
+
+#. module: base_action_rule
+#: model:ir.actions.act_window,name:base_action_rule.base_action_rule_act
+#: model:ir.ui.menu,name:base_action_rule.menu_base_action_rule_form
+msgid "Automated Actions"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,server_action_id:0
+msgid "Server Action"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,regex_name:0
+msgid "Regex on Resource Name"
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,act_remind_attach:0
+msgid ""
+"Check this if you want that all documents attached to the object be attached "
+"to the reminder email sent."
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Conditions on Timing"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,sequence:0
+msgid "Sequence"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Actions"
+msgstr ""
+
+#. module: base_action_rule
+#: help:base.action.rule,active:0
+msgid ""
+"If the active field is set to False, it will allow you to hide the rule "
+"without removing it."
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "%(object_user)s = Responsible name"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,create_date:0
+msgid "Create Date"
+msgstr ""
+
+#. module: base_action_rule
+#: view:base.action.rule:0
+msgid "Conditions on States"
+msgstr ""
+
+#. module: base_action_rule
+#: field:base.action.rule,trg_date_type:0
+msgid "Trigger Date"
+msgstr ""
diff --git a/addons/base_calendar/__openerp__.py b/addons/base_calendar/__openerp__.py
new file mode 100644
index 00000000000..6edb978301f
--- /dev/null
+++ b/addons/base_calendar/__openerp__.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+{
+ "name" : "Basic Calendar Functionality",
+ "version" : "1.0",
+ "depends" : ["base", "mail"],
+ 'complexity': "easy",
+ 'description': """
+This is a full-featured calendar system.
+========================================
+
+It supports:
+ - Calendar of events
+ - Alerts (create requests)
+ - Recurring events
+ - Invitations to people""",
+ "author" : "OpenERP SA",
+ 'category': 'Tools',
+ 'website': 'http://www.openerp.com',
+ "init_xml" : [
+ 'base_calendar_data.xml'
+ ],
+ "demo_xml" : [],
+ "update_xml" : [
+ 'security/calendar_security.xml',
+ 'security/ir.model.access.csv',
+ 'wizard/base_calendar_invite_attendee_view.xml',
+ 'base_calendar_view.xml'
+ ],
+ "test" : ['test/base_calendar_test.yml'],
+ "installable" : True,
+ "active" : False,
+ "certificate" : "00694071962960352821",
+ 'images': ['images/base_calendar1.jpeg','images/base_calendar2.jpeg','images/base_calendar3.jpeg','images/base_calendar4.jpeg',],
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py
index a128e18fcc1..3c6a36b965e 100644
--- a/addons/base_calendar/base_calendar.py
+++ b/addons/base_calendar/base_calendar.py
@@ -386,7 +386,7 @@ property or property parameter."),
}
def copy(self, cr, uid, id, default=None, context=None):
- raise osv.except_osv(_('Warning!'), _('Can not Duplicate'))
+ raise osv.except_osv(_('Warning!'), _('You cannot duplicate a calendar attendee.'))
def get_ics_file(self, cr, uid, event_obj, context=None):
"""
@@ -484,6 +484,7 @@ property or property parameter."),
context = {}
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
+ mail_message = self.pool.get('mail.message')
for att in self.browse(cr, uid, ids, context=context):
sign = att.sent_by_uid and att.sent_by_uid.signature or ''
sign = ' '.join(sign and sign.split('\n') or [])
@@ -510,14 +511,15 @@ property or property parameter."),
body = html_invitation % body_vals
if mail_to and email_from:
attach = self.get_ics_file(cr, uid, res_obj, context=context)
- tools.email_send(
+ mail_message.schedule_with_attach(cr, uid,
email_from,
mail_to,
sub,
body,
- attach=attach and [('invitation.ics', attach)] or None,
+ attachments=attach and {'invitation.ics': attach} or None,
subtype='html',
- reply_to=email_from
+ reply_to=email_from,
+ context=context
)
return True
@@ -812,6 +814,7 @@ class calendar_alarm(osv.osv):
"""
if context is None:
context = {}
+ mail_message = self.pool.get('mail.message')
current_datetime = datetime.now()
request_obj = self.pool.get('res.request')
alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
@@ -893,11 +896,12 @@ From:
for att in alarm.attendee_ids:
mail_to.append(att.user_id.user_email)
if mail_to:
- tools.email_send(
+ mail_message.schedule_with_attach(cr, uid,
tools.config.get('email_from', False),
mail_to,
sub,
- body
+ body,
+ context=context
)
if next_trigger_date:
update_vals.update({'trigger_date': next_trigger_date})
@@ -994,9 +998,9 @@ class calendar_event(osv.osv):
for datas in self.read(cr, uid, ids, ['id','byday','recurrency', 'month_list','end_date', 'rrule_type', 'select1', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'exrule', 'day', 'week_list' ], context=context):
event = datas['id']
if datas.get('interval', 0) < 0:
- raise osv.except_osv(_('Warning!'), _('Interval can not be Negative'))
+ raise osv.except_osv(_('Warning!'), _('Interval cannot be negative'))
if datas.get('count', 0) < 0:
- raise osv.except_osv(_('Warning!'), _('Count can not be Negative'))
+ raise osv.except_osv(_('Warning!'), _('Count cannot be negative'))
result[event] = self.compute_rule_string(datas)
return result
@@ -1038,7 +1042,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
- 'end_type' : fields.selection([('count', 'Fix amout of times'), ('end_date','End date')], 'Way to end reccurency'),
+ 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence termination'),
'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
'count': fields.integer('Repeat', help="Repeat x times"),
'mo': fields.boolean('Mon'),
@@ -1199,13 +1203,14 @@ rule or repeating pattern of time to exclude from the recurring rule."),
return (datas.get('end_type') == 'count' and (';COUNT=' + str(datas.get('count'))) or '') +\
((datas.get('end_date_new') and datas.get('end_type') == 'end_date' and (';UNTIL=' + datas.get('end_date_new'))) or '')
-
+
freq=datas.get('rrule_type')
if freq == 'none':
return ''
- interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
- return 'FREQ=' + freq.upper() + get_week_string(freq, datas) + interval_srting + get_end_date(datas) + get_month_string(freq, datas)
+ interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
+
+ return 'FREQ=' + freq.upper() + get_week_string(freq, datas) + interval_srting + get_end_date(datas) + get_month_string(freq, datas)
def remove_virtual_id(self, ids):
if isinstance(ids, (str, int)):
@@ -1216,9 +1221,8 @@ rule or repeating pattern of time to exclude from the recurring rule."),
for id in ids:
res.append(base_calendar_id2real_id(id))
return res
-
- def search(self, cr, uid, args, offset=0, limit=0, order=None,
- context=None, count=False):
+
+ def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
args_without_date = []
start_date = False
until_date = False
@@ -1250,7 +1254,6 @@ rule or repeating pattern of time to exclude from the recurring rule."),
else:
return res
-
def get_edit_all(self, cr, uid, id, vals=None):
"""
return true if we have to edit all meeting from the same recurrent
@@ -1277,7 +1280,8 @@ rule or repeating pattern of time to exclude from the recurring rule."),
return date_start == split_id[1]
except Exception:
return True
-
+
+
def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
if context is None:
context = {}
@@ -1289,7 +1293,6 @@ rule or repeating pattern of time to exclude from the recurring rule."),
res = False
for event_id in select:
real_event_id = base_calendar_id2real_id(event_id)
-
edit_all = self.get_edit_all(cr, uid, event_id, vals=vals)
if edit_all:
if self.need_to_update(event_id, vals):
diff --git a/addons/base_calendar/base_calendar_view.xml b/addons/base_calendar/base_calendar_view.xml
index 451152b2b58..a1d7adc0084 100644
--- a/addons/base_calendar/base_calendar_view.xml
+++ b/addons/base_calendar/base_calendar_view.xml
@@ -110,22 +110,22 @@
+ help="Invitations To Review" />
+ help="Accepted Invitations" />
+ help="Declined Invitations" />
+ help="Delegated Invitations" />
-
-
+
+
-
-
+
+ search
-
+
@@ -456,13 +456,13 @@
domain="[('state','=','confirmed')]"
help="Confirmed Events" />
-
-
+
+
-
+
-
-
+
+
diff --git a/addons/base_contact/base_contact_view.xml b/addons/base_contact/base_contact_view.xml
index 8029084b7c3..1dc73f16f72 100644
--- a/addons/base_contact/base_contact_view.xml
+++ b/addons/base_contact/base_contact_view.xml
@@ -108,9 +108,9 @@
search
-
-
-
+
+
+
@@ -344,7 +344,7 @@
-
+
diff --git a/addons/base_crypt/i18n/sl.po b/addons/base_crypt/i18n/sl.po
new file mode 100644
index 00000000000..d52c5723ba9
--- /dev/null
+++ b/addons/base_crypt/i18n/sl.po
@@ -0,0 +1,84 @@
+# Slovenian translation for openobject-addons
+# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2011-01-11 11:14+0000\n"
+"PO-Revision-Date: 2011-09-14 11:36+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Slovenian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2011-09-15 04:48+0000\n"
+"X-Generator: Launchpad (build 13921)\n"
+
+#. module: base_crypt
+#: sql_constraint:res.users:0
+msgid "You can not have two users with the same login !"
+msgstr "Ne morete imeti dva uporabnika z istim prijavnim imenom!"
+
+#. module: base_crypt
+#: model:ir.model,name:base_crypt.model_res_users
+msgid "res.users"
+msgstr "res.users"
+
+#. module: base_crypt
+#: constraint:res.users:0
+msgid "The chosen company is not in the allowed companies for this user"
+msgstr "Izbrano podjetje ni v dovoljenih podjetjih za tega uporabnika"
+
+#. module: base_crypt
+#: code:addons/base_crypt/crypt.py:132
+#, python-format
+msgid "Please specify the password !"
+msgstr "Prosim, navedite geslo !"
+
+#. module: base_crypt
+#: model:ir.module.module,shortdesc:base_crypt.module_meta_information
+msgid "Base - Password Encryption"
+msgstr "Osnovno - enkripcija gesla"
+
+#. module: base_crypt
+#: code:addons/base_crypt/crypt.py:132
+#, python-format
+msgid "Error"
+msgstr "Napaka"
+
+#. module: base_crypt
+#: model:ir.module.module,description:base_crypt.module_meta_information
+msgid ""
+"This module replaces the cleartext password in the database with a password "
+"hash,\n"
+"preventing anyone from reading the original password.\n"
+"For your existing user base, the removal of the cleartext passwords occurs "
+"the first time\n"
+"a user logs into the database, after installing base_crypt.\n"
+"After installing this module it won't be possible to recover a forgotten "
+"password for your\n"
+"users, the only solution is for an admin to set a new password.\n"
+"\n"
+"Note: installing this module does not mean you can ignore basic security "
+"measures,\n"
+"as the password is still transmitted unencrypted on the network (by the "
+"client),\n"
+"unless you are using a secure protocol such as XML-RPCS.\n"
+" "
+msgstr ""
+"Ta modul nadomešča obliko besedilnega geslo v bazi podatkov z hash geslom,\n"
+"ki preprečuje komurkoli branje izvirnega gesla.\n"
+"Za vašo obstoječo bazo uporabnikov, odstranitev gesla v obliki besedila se "
+"pojavi pri\n"
+"prvi prijavi uporabnika v bazo podatkov, po namestitvi base_crypt.\n"
+"Po namestitvi tega modula ne bo mogoče obnoviti pozabiljenega gesla za vaše\n"
+"uporabnike, edina rešitev je, da admin nastavi novo geslo.\n"
+"\n"
+"Opomba: namestitev tega modula ne pomeni ignoriranja osnovnih varnostnih "
+"ukrepov,\n"
+"ker se geslo še vedno prenaša nešifrirano v omrežju (preko klienta),\n"
+"razen če ne uporabljate varni protokol kot je XML-RPCS.\n"
+" "
diff --git a/addons/base_report_creator/base_report_creator.py b/addons/base_report_creator/base_report_creator.py
index 29699b9000d..dd2a69dc9c1 100644
--- a/addons/base_report_creator/base_report_creator.py
+++ b/addons/base_report_creator/base_report_creator.py
@@ -529,7 +529,7 @@ class report_creator(osv.osv):
_constraints = [
- (_function_field, 'You can not display field which are not stored in Database.', ['field_ids']),
+ (_function_field, 'You can not display field which are not stored in database.', ['field_ids']),
(_aggregation_error, 'You can apply aggregate function to the non calculated field.', ['field_ids']),
(_calander_view_error, "You must have to give calendar view's color,start date and delay.", ['field_ids']),
]
diff --git a/addons/base_setup/base_setup_views.xml b/addons/base_setup/base_setup_views.xml
index cb00ac3eb5b..afc64d3f83c 100644
--- a/addons/base_setup/base_setup_views.xml
+++ b/addons/base_setup/base_setup_views.xml
@@ -24,7 +24,6 @@
Install Applications
-
@@ -38,17 +37,17 @@
-
+
-
-
-
-
-
+
+
+
+
+
@@ -71,7 +70,6 @@
- 2
@@ -223,7 +221,6 @@
- 1
diff --git a/addons/base_synchro/base_synchro_view.xml b/addons/base_synchro/base_synchro_view.xml
index f161d6c31ea..07f71539bb7 100644
--- a/addons/base_synchro/base_synchro_view.xml
+++ b/addons/base_synchro/base_synchro_view.xml
@@ -36,12 +36,12 @@
search
-
+
-
+
@@ -118,12 +118,12 @@
search
-
+
-
+
diff --git a/addons/base_synchro/wizard/base_synchro.py b/addons/base_synchro/wizard/base_synchro.py
index 1a593684b59..bc16b558ce5 100644
--- a/addons/base_synchro/wizard/base_synchro.py
+++ b/addons/base_synchro/wizard/base_synchro.py
@@ -170,7 +170,7 @@ class base_synchro(osv.osv_memory):
#
# If not synchronized, try to find it with name_get/name_search
#
- names = pool_src.get(object).name_get(cr, uid, [id], context)[0][1]
+ names = pool_src.get(object).name_get(cr, uid, [id])[0][1]
res = pool_dest.get(object).name_search(cr, uid, names, [], 'like')
if res:
result = res[0][0]
@@ -189,7 +189,7 @@ class base_synchro(osv.osv_memory):
def data_transform(self, cr, uid, pool_src, pool_dest, object, data, action='u', context=None):
self.meta.setdefault(pool_src, {})
if not object in self.meta[pool_src]:
- self.meta[pool_src][object] = pool_src.get(object).fields_get(cr, uid, context)
+ self.meta[pool_src][object] = pool_src.get(object).fields_get(cr, uid)
fields = self.meta[pool_src][object]
for f in fields:
diff --git a/addons/base_synchro/wizard/base_synchro_view.xml b/addons/base_synchro/wizard/base_synchro_view.xml
index 231fc7b055f..ccebb6145c2 100644
--- a/addons/base_synchro/wizard/base_synchro_view.xml
+++ b/addons/base_synchro/wizard/base_synchro_view.xml
@@ -7,6 +7,7 @@
Base Synchronizationbase.synchroform
+ 1
-
+
+
@@ -174,52 +174,53 @@
-
+
-
+
-
-
-
-
-
-
-
- Send Mail
- crm.send.mail
- form
- form
-
- new
-
-
-
-
-
-
-
-
- crm.mail.reply.form
- crm.send.mail
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Reply to last Mail
- crm.send.mail
- form
- form
-
- new
-
-
-
-
diff --git a/addons/crm/wizard/mail_compose_message.py b/addons/crm/wizard/mail_compose_message.py
new file mode 100644
index 00000000000..512d9ece77a
--- /dev/null
+++ b/addons/crm/wizard/mail_compose_message.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from osv import osv
+from osv import fields
+import tools
+
+SUPPORTED_MODELS = ['crm.lead',]
+
+class mail_compose_message(osv.osv_memory):
+ _inherit = 'mail.compose.message'
+
+ def get_value(self, cr, uid, model, res_id, context=None):
+ """Returns a defaults-like dict with initial values for the composition
+ wizard when sending an email related to the document record identified
+ by ``model`` and ``res_id``.
+
+ Overrides the default implementation to provide more default field values
+ related to the corresponding CRM case.
+
+ :param str model: model name of the document record this mail is related to.
+ :param int res_id: id of the document record this mail is related to.
+ :param dict context: several context values will modify the behavior
+ of the wizard, cfr. the class description.
+ """
+ result = super(mail_compose_message, self).get_value(cr, uid, model, res_id, context=context)
+ if model in SUPPORTED_MODELS and res_id:
+ model_obj = self.pool.get(model)
+ data = model_obj.browse(cr, uid , res_id, context)
+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ result.update({
+ 'subject' : data.name or False,
+ 'email_to' : data.email_from or False,
+ 'email_from' : user.user_email or tools.config.get('email_from', False),
+ 'body_text' : '\n' + tools.ustr(user.signature),
+ 'email_cc' : tools.ustr(data.email_cc or ''),
+ 'model': model,
+ 'res_id': res_id,
+ 'subtype': 'plain',
+ })
+ if hasattr(data, 'section_id'):
+ result.update({'reply_to' : data.section_id and data.section_id.reply_to or False})
+ return result
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/wizard/wizard_history_event.py b/addons/crm/wizard/wizard_history_event.py
index caaf37a88d8..bb501e0b750 100644
--- a/addons/crm/wizard/wizard_history_event.py
+++ b/addons/crm/wizard/wizard_history_event.py
@@ -32,7 +32,7 @@ def _open_history_event(self, cr, uid, data, context=None):
if id2:
id2 = data_obj.browse(cr, uid, id2, context=context).res_id
res = ''
- if data.get('model',False) and data.get('ids',False):
+ if data.get('model') and data.get('ids'):
model_obj = pooler.get_pool(cr.dbname).get(data['model'])
res = model_obj.browse(cr, uid, data['ids'], context=context)
if len(res):
diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py
index 401c43b0455..9700a5a24b2 100644
--- a/addons/crm_claim/crm_claim.py
+++ b/addons/crm_claim/crm_claim.py
@@ -22,10 +22,12 @@
from osv import fields, osv
from crm import crm
import time
+from crm import wizard
import binascii
import tools
from tools.translate import _
+wizard.mail_compose_message.SUPPORTED_MODELS.append('crm.claim')
CRM_CLAIM_PENDING_STATES = (
crm.AVAILABLE_STATES[2][0], # Cancelled
crm.AVAILABLE_STATES[3][0], # Done
@@ -40,69 +42,69 @@ class crm_claim(crm.crm_case, osv.osv):
_name = "crm.claim"
_description = "Claim"
_order = "priority,date desc"
- _inherit = ['mailgate.thread']
+ _inherit = ['mail.thread']
_columns = {
- 'id': fields.integer('ID', readonly=True),
- 'name': fields.char('Claim Subject', size=128, required=True),
+ 'id': fields.integer('ID', readonly=True),
+ 'name': fields.char('Claim Subject', size=128, required=True),
'action_next': fields.char('Next Action', size=200),
'date_action_next': fields.datetime('Next Action Date'),
- 'description': fields.text('Description'),
- 'resolution': fields.text('Resolution'),
- 'create_date': fields.datetime('Creation Date' , readonly=True),
- 'write_date': fields.datetime('Update Date' , readonly=True),
- 'date_deadline': fields.date('Deadline'),
- 'date_closed': fields.datetime('Closed', readonly=True),
- 'date': fields.datetime('Claim Date'),
- 'ref' : fields.reference('Reference', selection=crm._links_get, size=128),
+ 'description': fields.text('Description'),
+ 'resolution': fields.text('Resolution'),
+ 'create_date': fields.datetime('Creation Date' , readonly=True),
+ 'write_date': fields.datetime('Update Date' , readonly=True),
+ 'date_deadline': fields.date('Deadline'),
+ 'date_closed': fields.datetime('Closed', readonly=True),
+ 'date': fields.datetime('Claim Date'),
+ 'ref' : fields.reference('Reference', selection=crm._links_get, size=128),
'categ_id': fields.many2one('crm.case.categ', 'Category', \
domain="[('section_id','=',section_id),\
- ('object_id.model', '=', 'crm.claim')]"),
- 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
+ ('object_id.model', '=', 'crm.claim')]"),
+ 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
'type_action': fields.selection([('correction','Corrective Action'),('prevention','Preventive Action')], 'Action Type'),
- 'user_id': fields.many2one('res.users', 'Responsible'),
- 'user_fault': fields.char('Trouble Responsible', size=64),
+ 'user_id': fields.many2one('res.users', 'Responsible'),
+ 'user_fault': fields.char('Trouble Responsible', size=64),
'section_id': fields.many2one('crm.case.section', 'Sales Team', \
select=True, help="Sales team to which Case belongs to."\
"Define Responsible user and Email account for"\
- " mail gateway."),
- 'company_id': fields.many2one('res.company', 'Company'),
- 'partner_id': fields.many2one('res.partner', 'Partner'),
+ " mail gateway."),
+ 'company_id': fields.many2one('res.company', 'Company'),
+ 'partner_id': fields.many2one('res.partner', 'Partner'),
'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
# domain="[('partner_id','=',partner_id)]"
- ),
- 'email_cc': fields.text('Watchers Emails', size=252, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
- 'email_from': fields.char('Email', size=128, help="These people will receive email."),
- 'partner_phone': fields.char('Phone', size=32),
+ ),
+ 'email_cc': fields.text('Watchers Emails', size=252, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
+ 'email_from': fields.char('Email', size=128, help="These people will receive email."),
+ 'partner_phone': fields.char('Phone', size=32),
'stage_id': fields.many2one ('crm.case.stage', 'Stage', domain="[('section_ids','=',section_id)]"),
- 'cause': fields.text('Root Cause'),
- 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
+ 'cause': fields.text('Root Cause'),
+ 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
help='The state is set to \'Draft\', when a case is created.\
\nIf the case is in progress the state is set to \'Open\'.\
\nWhen the case is over, the state is set to \'Done\'.\
- \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+ \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
}
_defaults = {
- 'user_id': crm.crm_case._get_default_user,
- 'partner_id': crm.crm_case._get_default_partner,
- 'partner_address_id': crm.crm_case._get_default_partner_address,
- 'email_from':crm.crm_case. _get_default_email,
- 'state': lambda *a: 'draft',
- 'section_id': crm.crm_case._get_section,
+ 'user_id': crm.crm_case._get_default_user,
+ 'partner_id': crm.crm_case._get_default_partner,
+ 'partner_address_id': crm.crm_case._get_default_partner_address,
+ 'email_from':crm.crm_case. _get_default_email,
+ 'state': lambda *a: 'draft',
+ 'section_id':crm.crm_case. _get_section,
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
- 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
+ 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
}
-
+
def onchange_partner_id(self, cr, uid, ids, part, email=False):
"""This function returns value of partner address based on partner
- @param part: Partner's id
- @email: Partner's email ID
+ :param part: Partner's id
+ :param email: ignored
"""
if not part:
return {'value': {'partner_address_id': False,
- 'email_from': False,
+ 'email_from': False,
'partner_phone': False,
'partner_mobile': False
}}
@@ -113,8 +115,8 @@ class crm_claim(crm.crm_case, osv.osv):
def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
"""This function returns value of partner email based on Partner Address
- @param add: Id of Partner's address
- @email: Partner's email ID
+ :param part: Partner's id
+ :param email: ignored
"""
if not add:
return {'value': {'email_from': False}}
@@ -122,6 +124,7 @@ class crm_claim(crm.crm_case, osv.osv):
return {'value': {'email_from': address.email, 'partner_phone': address.phone, 'partner_mobile': address.mobile}}
def case_open(self, cr, uid, ids, *args):
+ """Opens Claim"""
for l in self.browse(cr, uid, ids):
# When coming from draft override date and stage otherwise just set state
if l.state == 'draft':
@@ -135,16 +138,13 @@ class crm_claim(crm.crm_case, osv.osv):
res = super(crm_claim, self).case_open(cr, uid, ids, *args)
return res
- def message_new(self, cr, uid, msg, context=None):
- """ Automatically calls when new email message arrives
- """
- mailgate_pool = self.pool.get('email.server.tools')
-
- subject = msg.get('subject') or _("No Subject")
- body = msg.get('body')
+ def message_new(self, cr, uid, msg, custom_values=None, context=None):
+ """Automatically called when new email message arrives"""
+ res_id = super(crm_claim,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
+ subject = msg.get('subject')
+ body = msg.get('body_text')
msg_from = msg.get('from')
priority = msg.get('priority')
-
vals = {
'name': subject,
'email_from': msg_from,
@@ -152,35 +152,18 @@ class crm_claim(crm.crm_case, osv.osv):
'description': body,
'user_id': False,
}
- if msg.get('priority', False):
+ if priority:
vals['priority'] = priority
+ vals.update(self.message_partner_by_email(cr, uid, msg.get('from', False)))
+ self.write(cr, uid, [res_id], vals, context=context)
+ return res_id
- res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
- if res:
- vals.update(res)
-
- res = self.create(cr, uid, vals, context)
- attachents = msg.get('attachments', [])
- for attactment in attachents or []:
- data_attach = {
- 'name': attactment,
- 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
- 'datas_fname': attactment,
- 'description': 'Mail attachment',
- 'res_model': self._name,
- 'res_id': res,
- }
- self.pool.get('ir.attachment').create(cr, uid, data_attach)
-
- return res
-
- def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
- """
- @param ids: List of update mail’s IDs
- """
+ def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None):
if isinstance(ids, (str, int, long)):
ids = [ids]
+ res_id = super(crm_claim,self).message_update(cr, uid, ids, msg, context=context)
+
if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
vals['priority'] = msg.get('priority')
@@ -190,7 +173,7 @@ class crm_claim(crm.crm_case, osv.osv):
'probability':'probability'
}
vls = {}
- for line in msg['body'].split('\n'):
+ for line in msg['body_text'].split('\n'):
line = line.strip()
res = tools.misc.command_re.match(line)
if res and maps.get(res.group(1).lower()):
@@ -208,19 +191,10 @@ class crm_claim(crm.crm_case, osv.osv):
res = self.write(cr, uid, [case.id], values, context=context)
return res
- def msg_send(self, cr, uid, id, *args, **argv):
- """ Send The Message
- @param ids: List of email’s IDs
- """
- return True
-
-crm_claim()
-
class res_partner(osv.osv):
_inherit = 'res.partner'
_columns = {
'claims_ids': fields.one2many('crm.claim', 'partner_id', 'Claims'),
}
-res_partner()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm_claim/crm_claim_view.xml b/addons/crm_claim/crm_claim_view.xml
index 72dcabf7c9a..15956389e8e 100644
--- a/addons/crm_claim/crm_claim_view.xml
+++ b/addons/crm_claim/crm_claim_view.xml
@@ -70,15 +70,10 @@
-
-
-
-
-
@@ -159,12 +154,12 @@
-
-
+
+
-
-
+
@@ -82,7 +80,7 @@
Define the delivery methods you are using and their pricing in order to reinvoice the delivery costs when you are doing invoicing based on delivery orders
-
+
@@ -132,7 +130,7 @@
tree,formThe delivery price list allows you to compute the cost and sales price of the delivery according to the weight of the products and other criteria. You can define several price lists for one delivery method, per country or a zone in a specific country defined by a postal code range.
-
+ delivery.grid.line.form
diff --git a/addons/delivery/i18n/da.po b/addons/delivery/i18n/da.po
new file mode 100644
index 00000000000..4eee19c0610
--- /dev/null
+++ b/addons/delivery/i18n/da.po
@@ -0,0 +1,516 @@
+# Danish translation for openobject-addons
+# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2011-01-11 11:15+0000\n"
+"PO-Revision-Date: 2011-09-18 16:52+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Danish \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2011-09-19 04:41+0000\n"
+"X-Generator: Launchpad (build 13955)\n"
+
+#. module: delivery
+#: report:sale.shipping:0
+msgid "Order Ref."
+msgstr ""
+
+#. module: delivery
+#: model:product.template,name:delivery.delivery_product_product_template
+msgid "Delivery by Poste"
+msgstr ""
+
+#. module: delivery
+#: view:delivery.grid:0
+msgid "Destination"
+msgstr "Mål"
+
+#. module: delivery
+#: field:stock.move,weight_net:0
+msgid "Net weight"
+msgstr "Netto vægt"
+
+#. module: delivery
+#: view:stock.picking:0
+msgid "Delivery Order"
+msgstr ""
+
+#. module: delivery
+#: code:addons/delivery/delivery.py:141
+#, python-format
+msgid "No price available !"
+msgstr "Ingen pris tilgængelig"
+
+#. module: delivery
+#: model:ir.model,name:delivery.model_delivery_grid_line
+msgid "Delivery Grid Line"
+msgstr ""
+
+#. module: delivery
+#: view:delivery.grid:0
+msgid "Delivery grids"
+msgstr ""
+
+#. module: delivery
+#: selection:delivery.grid.line,type:0
+#: selection:delivery.grid.line,variable_factor:0
+#: field:stock.picking,volume:0
+msgid "Volume"
+msgstr ""
+
+#. module: delivery
+#: sql_constraint:sale.order:0
+msgid "Order Reference must be unique !"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.grid,line_ids:0
+msgid "Grid Line"
+msgstr ""
+
+#. module: delivery
+#: model:ir.actions.report.xml,name:delivery.report_shipping
+msgid "Delivery order"
+msgstr ""
+
+#. module: delivery
+#: view:res.partner:0
+msgid "Deliveries Properties"
+msgstr ""
+
+#. module: delivery
+#: model:ir.actions.act_window,name:delivery.action_picking_tree4
+msgid "Picking to be invoiced"
+msgstr ""
+
+#. module: delivery
+#: help:delivery.grid,sequence:0
+msgid "Gives the sequence order when displaying a list of delivery grid."
+msgstr ""
+
+#. module: delivery
+#: view:delivery.grid:0
+#: field:delivery.grid,country_ids:0
+msgid "Countries"
+msgstr "Lande"
+
+#. module: delivery
+#: report:sale.shipping:0
+msgid "Delivery Order :"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.grid.line,variable_factor:0
+msgid "Variable Factor"
+msgstr ""
+
+#. module: delivery
+#: model:ir.actions.act_window,help:delivery.action_delivery_grid_form
+msgid ""
+"The delivery price list allows you to compute the cost and sales price of "
+"the delivery according to the weight of the products and other criteria. You "
+"can define several price lists for one delivery method, per country or a "
+"zone in a specific country defined by a postal code range."
+msgstr ""
+
+#. module: delivery
+#: selection:delivery.grid.line,price_type:0
+msgid "Fixed"
+msgstr ""
+
+#. module: delivery
+#: view:delivery.sale.order:0
+#: field:delivery.sale.order,carrier_id:0
+#: model:ir.actions.act_window,name:delivery.action_delivery_carrier_form
+#: model:ir.ui.menu,name:delivery.menu_action_delivery_carrier_form
+#: field:res.partner,property_delivery_carrier:0
+#: field:sale.order,carrier_id:0
+msgid "Delivery Method"
+msgstr ""
+
+#. module: delivery
+#: model:ir.model,name:delivery.model_stock_move
+msgid "Stock Move"
+msgstr ""
+
+#. module: delivery
+#: code:addons/delivery/delivery.py:141
+#, python-format
+msgid "No line matched this order in the choosed delivery grids !"
+msgstr ""
+
+#. module: delivery
+#: field:stock.picking,carrier_tracking_ref:0
+msgid "Carrier Tracking Ref"
+msgstr ""
+
+#. module: delivery
+#: field:stock.picking,weight_net:0
+msgid "Net Weight"
+msgstr "Netto vægt"
+
+#. module: delivery
+#: model:ir.actions.act_window,help:delivery.action_delivery_carrier_form
+msgid ""
+"Create and manage the delivery methods you need for your sales activities. "
+"Each delivery method can be assigned to a price list which computes the "
+"price of the delivery according to the products sold or delivered."
+msgstr ""
+
+#. module: delivery
+#: code:addons/delivery/stock.py:98
+#, python-format
+msgid "Warning"
+msgstr "Advarsel!"
+
+#. module: delivery
+#: view:delivery.grid:0
+msgid "Grid definition"
+msgstr ""
+
+#. module: delivery
+#: view:delivery.sale.order:0
+msgid "_Cancel"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.grid.line,operator:0
+msgid "Operator"
+msgstr ""
+
+#. module: delivery
+#: model:ir.model,name:delivery.model_res_partner
+msgid "Partner"
+msgstr ""
+
+#. module: delivery
+#: model:ir.model,name:delivery.model_sale_order
+msgid "Sales Order"
+msgstr "Salgsordre"
+
+#. module: delivery
+#: model:ir.model,name:delivery.model_delivery_grid
+msgid "Delivery Grid"
+msgstr ""
+
+#. module: delivery
+#: report:sale.shipping:0
+msgid "Invoiced to"
+msgstr ""
+
+#. module: delivery
+#: model:ir.model,name:delivery.model_stock_picking
+msgid "Picking List"
+msgstr ""
+
+#. module: delivery
+#: model:ir.model,name:delivery.model_delivery_sale_order
+msgid "Make Delievery"
+msgstr ""
+
+#. module: delivery
+#: model:ir.module.module,description:delivery.module_meta_information
+msgid ""
+"Allows you to add delivery methods in sale orders and picking.\n"
+" You can define your own carrier and delivery grids for prices.\n"
+" When creating invoices from picking, OpenERP is able to add and compute "
+"the shipping line.\n"
+"\n"
+" "
+msgstr ""
+
+#. module: delivery
+#: view:delivery.grid.line:0
+msgid "Grid Lines"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.grid.line,grid_id:0
+msgid "Grid"
+msgstr ""
+
+#. module: delivery
+#: help:delivery.grid,active:0
+msgid ""
+"If the active field is set to False, it will allow you to hide the delivery "
+"grid without removing it."
+msgstr ""
+
+#. module: delivery
+#: field:delivery.grid,zip_to:0
+msgid "To Zip"
+msgstr ""
+
+#. module: delivery
+#: report:sale.shipping:0
+msgid "Order Date"
+msgstr "Ordredato"
+
+#. module: delivery
+#: field:delivery.grid,name:0
+msgid "Grid Name"
+msgstr ""
+
+#. module: delivery
+#: view:stock.move:0
+msgid "Weights"
+msgstr "Vægte"
+
+#. module: delivery
+#: field:stock.picking,number_of_packages:0
+msgid "Number of Packages"
+msgstr ""
+
+#. module: delivery
+#: selection:delivery.grid.line,type:0
+#: selection:delivery.grid.line,variable_factor:0
+#: report:sale.shipping:0
+#: field:stock.move,weight:0
+#: field:stock.picking,weight:0
+msgid "Weight"
+msgstr "Vægt"
+
+#. module: delivery
+#: help:delivery.carrier,active:0
+msgid ""
+"If the active field is set to False, it will allow you to hide the delivery "
+"carrier without removing it."
+msgstr ""
+
+#. module: delivery
+#: code:addons/delivery/wizard/delivery_sale_order.py:95
+#, python-format
+msgid "No grid available !"
+msgstr ""
+
+#. module: delivery
+#: selection:delivery.grid.line,operator:0
+msgid ">="
+msgstr ""
+
+#. module: delivery
+#: code:addons/delivery/wizard/delivery_sale_order.py:66
+#: code:addons/delivery/wizard/delivery_sale_order.py:98
+#, python-format
+msgid "Order not in draft state !"
+msgstr ""
+
+#. module: delivery
+#: constraint:res.partner:0
+msgid "Error ! You can not create recursive associated members."
+msgstr ""
+
+#. module: delivery
+#: report:sale.shipping:0
+msgid "Lot"
+msgstr ""
+
+#. module: delivery
+#: constraint:stock.move:0
+msgid "You try to assign a lot which is not from the same product"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.carrier,active:0
+#: field:delivery.grid,active:0
+msgid "Active"
+msgstr ""
+
+#. module: delivery
+#: report:sale.shipping:0
+msgid "Shipping Date"
+msgstr "Forsendelses dato"
+
+#. module: delivery
+#: field:delivery.carrier,product_id:0
+msgid "Delivery Product"
+msgstr ""
+
+#. module: delivery
+#: view:delivery.grid.line:0
+msgid "Condition"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.grid.line,standard_price:0
+msgid "Cost Price"
+msgstr "Kost pris"
+
+#. module: delivery
+#: selection:delivery.grid.line,price_type:0
+#: field:delivery.grid.line,type:0
+msgid "Variable"
+msgstr ""
+
+#. module: delivery
+#: help:res.partner,property_delivery_carrier:0
+msgid "This delivery method will be used when invoicing from picking."
+msgstr ""
+
+#. module: delivery
+#: field:delivery.grid.line,max_value:0
+msgid "Maximum Value"
+msgstr ""
+
+#. module: delivery
+#: report:sale.shipping:0
+msgid "Quantity"
+msgstr "Mængde"
+
+#. module: delivery
+#: field:delivery.grid,zip_from:0
+msgid "Start Zip"
+msgstr ""
+
+#. module: delivery
+#: help:sale.order,carrier_id:0
+msgid ""
+"Complete this field if you plan to invoice the shipping based on picking."
+msgstr ""
+
+#. module: delivery
+#: field:delivery.carrier,partner_id:0
+msgid "Carrier Partner"
+msgstr ""
+
+#. module: delivery
+#: view:res.partner:0
+msgid "Sales & Purchases"
+msgstr ""
+
+#. module: delivery
+#: selection:delivery.grid.line,operator:0
+msgid "<="
+msgstr ""
+
+#. module: delivery
+#: constraint:stock.move:0
+msgid "You must assign a production lot for this product"
+msgstr ""
+
+#. module: delivery
+#: view:delivery.sale.order:0
+msgid "Create Deliveries"
+msgstr ""
+
+#. module: delivery
+#: model:ir.actions.act_window,name:delivery.action_delivery_cost
+#: view:sale.order:0
+msgid "Delivery Costs"
+msgstr ""
+
+#. module: delivery
+#: report:sale.shipping:0
+msgid "Description"
+msgstr "Beskrivelse"
+
+#. module: delivery
+#: model:ir.actions.act_window,name:delivery.action_delivery_grid_form
+#: model:ir.ui.menu,name:delivery.menu_action_delivery_grid_form
+msgid "Delivery Pricelist"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.carrier,price:0
+#: selection:delivery.grid.line,type:0
+#: selection:delivery.grid.line,variable_factor:0
+msgid "Price"
+msgstr "Pris"
+
+#. module: delivery
+#: code:addons/delivery/wizard/delivery_sale_order.py:95
+#, python-format
+msgid "No grid matching for this carrier !"
+msgstr ""
+
+#. module: delivery
+#: model:ir.ui.menu,name:delivery.menu_delivery
+msgid "Delivery"
+msgstr "Levering"
+
+#. module: delivery
+#: selection:delivery.grid.line,type:0
+#: selection:delivery.grid.line,variable_factor:0
+msgid "Weight * Volume"
+msgstr ""
+
+#. module: delivery
+#: selection:delivery.grid.line,operator:0
+msgid "="
+msgstr ""
+
+#. module: delivery
+#: code:addons/delivery/stock.py:99
+#, python-format
+msgid "The carrier %s (id: %d) has no delivery grid!"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.grid.line,name:0
+msgid "Name"
+msgstr "Navn"
+
+#. module: delivery
+#: view:delivery.carrier:0
+#: field:delivery.carrier,name:0
+#: field:delivery.grid,carrier_id:0
+#: model:ir.model,name:delivery.model_delivery_carrier
+#: report:sale.shipping:0
+#: field:stock.picking,carrier_id:0
+msgid "Carrier"
+msgstr ""
+
+#. module: delivery
+#: view:delivery.sale.order:0
+msgid "_Apply"
+msgstr ""
+
+#. module: delivery
+#: field:sale.order,id:0
+msgid "ID"
+msgstr "ID"
+
+#. module: delivery
+#: code:addons/delivery/wizard/delivery_sale_order.py:66
+#: code:addons/delivery/wizard/delivery_sale_order.py:98
+#, python-format
+msgid "The order state have to be draft to add delivery lines."
+msgstr ""
+
+#. module: delivery
+#: model:ir.module.module,shortdesc:delivery.module_meta_information
+msgid "Carriers and deliveries"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.carrier,grids_id:0
+msgid "Delivery Grids"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.grid,sequence:0
+msgid "Sequence"
+msgstr "Rækkefølge"
+
+#. module: delivery
+#: field:delivery.grid.line,list_price:0
+msgid "Sale Price"
+msgstr "Salgspris"
+
+#. module: delivery
+#: view:delivery.grid:0
+#: field:delivery.grid,state_ids:0
+msgid "States"
+msgstr ""
+
+#. module: delivery
+#: field:delivery.grid.line,price_type:0
+msgid "Price Type"
+msgstr ""
diff --git a/addons/document/document.py b/addons/document/document.py
index 69e123b87eb..774c4e53a80 100644
--- a/addons/document/document.py
+++ b/addons/document/document.py
@@ -64,7 +64,7 @@ class document_file(osv.osv):
"SET parent_id = %s, db_datas = decode(encode(db_datas,'escape'), 'base64') " \
"WHERE parent_id IS NULL", (parent_id,))
- cr.execute("UPDATE ir_attachment SET file_size=length(db_datas) WHERE file_size = 0;")
+ cr.execute("UPDATE ir_attachment SET file_size=length(db_datas) WHERE file_size = 0 and type = 'binary'")
cr.execute("ALTER TABLE ir_attachment ALTER parent_id SET NOT NULL")
@@ -252,7 +252,7 @@ class document_file(osv.osv):
ids = ids2
if 'file_size' in vals: # only write that field using direct SQL calls
del vals['file_size']
- if len(ids) and len(vals):
+ if ids and vals:
result = super(document_file,self).write(cr, uid, ids, vals, context=context)
cr.commit() # ?
return result
diff --git a/addons/document/report/document_report_view.xml b/addons/document/report/document_report_view.xml
index b9a1652388c..e7fa4efb5be 100644
--- a/addons/document/report/document_report_view.xml
+++ b/addons/document/report/document_report_view.xml
@@ -41,15 +41,15 @@
search
-
+
-
-
-
-
+
+
+
+
diff --git a/addons/email_template/__init__.py b/addons/email_template/__init__.py
index dd87232679d..97931ea8bd5 100644
--- a/addons/email_template/__init__.py
+++ b/addons/email_template/__init__.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2010-2010 OpenERP SA ()
+# Copyright (C) 2010-Today OpenERP SA ()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,9 +19,7 @@
# along with this program. If not, see
#
##############################################################################
-
-import email_template_account
import email_template
-import email_template_mailbox
import wizard
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/email_template/__openerp__.py b/addons/email_template/__openerp__.py
index f56062f37ed..e5f83f9ac0c 100644
--- a/addons/email_template/__openerp__.py
+++ b/addons/email_template/__openerp__.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2010-2010 OpenERP SA ()
+# Copyright (C) 2010-Today OpenERP SA ()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,32 +21,47 @@
##############################################################################
{
- "name" : "Email Template for OpenERP",
- "version" : "0.7 RC",
+ "name" : "Email Templates",
+ "version" : "1.1",
"author" : "Openlabs",
"website" : "http://openerp.com",
"category" : "Tools",
'complexity': "expert",
- "depends" : ['marketing', 'base_tools'],
+ "depends" : ['mail'],
"description": """
-Email Template is extraction of Power Email basically just to send emails.
-==========================================================================
+Email Templating (simplified version of the original Power Email by Openlabs)
+=============================================================================
-You can define email accounts(server, port, mail format - HTML/Text/Both)
-and email templates (resource, recipient, subject, body, attachments).
+Lets you design complete email templates related to any OpenERP document (Sale
+Orders, Invoices and so on), including sender, recipient, subject, body (HTML and
+Text). You may also automatically attach files to your templates, or print and
+attach a report.
+
+For advanced use, the templates may include dynamic attributes of the document
+they are related to. For example, you may use the name of a Partner's country
+when writing to them, also providing a safe default in case the attribute is
+not defined. Each template contains a built-in assistant to help with the
+inclusion of these dynamic values.
+
+If you enable the option, a composition assistant will also appear in the sidebar
+of the OpenERP documents to which the template applies (e.g. Invoices).
+This serves as a quick way to send a new email based on the template, after
+reviewing and adapting the contents, if needed.
+This composition assistant will also turn into a mass mailing system when called
+for multiple documents at once.
+
+These email templates are also at the heart of the marketing campaign system
+(see the ``marketing_campaign`` application), if you need to automate larger
+campaigns on any OpenERP document.
+
+Technical note: only the templating system of the original Power Email by
+Openlabs was kept
-For each email template, you can have OpenERP generate a Wizard Action / Button
-that will be related to the object. So if you choose to do marketing campaigns
-for leads, the action will be added to the right side panel of the Lead form.
""",
- "init_xml": ['email_template_scheduler_data.xml'],
- "update_xml": [
- 'security/email_template_security.xml',
- 'email_template_workflow.xml',
- 'email_template_account_view.xml',
+ "data": [
+ 'wizard/email_template_preview_view.xml',
'email_template_view.xml',
- 'email_template_mailbox_view.xml',
- 'wizard/email_template_send_wizard_view.xml',
+ 'wizard/email_compose_message_view.xml',
'security/ir.model.access.csv'
],
"installable": True,
diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py
index aabc211df11..3917b3f2ddb 100644
--- a/addons/email_template/email_template.py
+++ b/addons/email_template/email_template.py
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2010-2010 OpenERP SA ()
+# Copyright (C) 2010-Today OpenERP SA ()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,277 +21,186 @@
##############################################################################
import base64
-import random
-import netsvc
import logging
-import re
-TEMPLATE_ENGINES = []
-
-from osv import osv, fields
+import netsvc
+from osv import osv
+from osv import fields
+import tools
from tools.translate import _
try:
from mako.template import Template as MakoTemplate
- TEMPLATE_ENGINES.append(('mako', 'Mako Templates'))
except ImportError:
- logging.getLogger('init').warning("module email_template: Mako templates not installed")
-
-try:
- from django.template import Context, Template as DjangoTemplate
- #Workaround for bug:
- #http://code.google.com/p/django-tagging/issues/detail?id=110
- from django.conf import settings
- settings.configure()
- #Workaround ends
- TEMPLATE_ENGINES.append(('django', 'Django Template'))
-except ImportError:
- logging.getLogger('init').warning("module email_template: Django templates not installed")
-
-import tools
-import pooler
-import logging
-
-def get_value(cursor, user, recid, message=None, template=None, context=None):
- """
- Evaluates an expression and returns its value
- @param cursor: Database Cursor
- @param user: ID of current user
- @param recid: ID of the target record under evaluation
- @param message: The expression to be evaluated
- @param template: BrowseRecord object of the current template
- @param context: OpenERP Context
- @return: Computed message (unicode) or u""
- """
- pool = pooler.get_pool(cursor.dbname)
- if message is None:
- message = {}
- #Returns the computed expression
- if message:
- try:
- message = tools.ustr(message)
- object = pool.get(template.model_int_name).browse(cursor, user, recid, context=context)
- env = {
- 'user':pool.get('res.users').browse(cursor, user, user, context=context),
- 'db':cursor.dbname
- }
- if template.template_language == 'mako':
- templ = MakoTemplate(message, input_encoding='utf-8')
- reply = MakoTemplate(message).render_unicode(object=object,
- peobject=object,
- env=env,
- format_exceptions=True)
- elif template.template_language == 'django':
- templ = DjangoTemplate(message)
- env['object'] = object
- env['peobject'] = object
- reply = templ.render(Context(env))
- return reply or False
- except Exception:
- logging.exception("can't render %r", message)
- return u""
- else:
- return message
+ logging.getLogger('init').warning("email_template: mako templates not available, templating features will not work!")
class email_template(osv.osv):
- "Templates for sending Email"
-
+ "Templates for sending email"
+ _inherit = 'mail.message'
_name = "email.template"
- _description = 'Email Templates for Models'
+ _description = 'Email Templates'
+ _rec_name = 'name' # override mail.message's behavior
- def change_model(self, cursor, user, ids, object_name, context=None):
- if object_name:
- mod_name = self.pool.get('ir.model').read(
- cursor,
- user,
- object_name,
- ['model'], context)['model']
+ def render_template(self, cr, uid, template, model, res_id, context=None):
+ """Render the given template text, replace mako expressions ``${expr}``
+ with the result of evaluating these expressions with
+ an evaluation context containing:
+
+ * ``user``: browse_record of the current user
+ * ``object``: browse_record of the document record this mail is
+ related to
+ * ``context``: the context passed to the mail composition wizard
+
+ :param str template: the template text to render
+ :param str model: model name of the document record this mail is related to.
+ :param int res_id: id of the document record this mail is related to.
+ """
+ if not template: return u""
+ try:
+ template = tools.ustr(template)
+ record = None
+ if res_id:
+ record = self.pool.get(model).browse(cr, uid, res_id, context=context)
+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ result = MakoTemplate(template).render_unicode(object=record,
+ user=user,
+ # context kw would clash with mako internals
+ ctx=context,
+ format_exceptions=True)
+ if result == u'False':
+ result = u''
+ return result
+ except Exception:
+ logging.exception("failed to render mako template value %r", template)
+ return u""
+
+ def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None):
+ if context is None:
+ context = {}
+ if not template_id:
+ return False
+ template = self.browse(cr, uid, template_id, context)
+ lang = self.render_template(cr, uid, template.lang, template.model, record_id, context)
+ if lang:
+ # Use translated template if necessary
+ ctx = context.copy()
+ ctx['lang'] = lang
+ template = self.browse(cr, uid, template.id, ctx)
else:
- mod_name = False
- return {
- 'value':{'model_int_name':mod_name}
- }
+ template = self.browse(cr, uid, int(template_id), context)
+ return template
+
+ def onchange_model_id(self, cr, uid, ids, model_id, context=None):
+ mod_name = False
+ if model_id:
+ mod_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
+ return {'value':{'model': mod_name}}
_columns = {
- 'name' : fields.char('Name', size=100, required=True),
- 'object_name':fields.many2one('ir.model', 'Resource'),
- 'model_int_name':fields.char('Model Internal Name', size=200,),
- 'from_account':fields.many2one(
- 'email_template.account',
- string="Email Account",
- help="Emails will be sent from this approved account."),
- 'def_to':fields.char(
- 'Recipient (To)',
- size=250,
- help="The Recipient of email. "
- "Placeholders can be used here. "
- "e.g. ${object.email_to}"),
- 'def_cc':fields.char(
- 'CC',
- size=250,
- help="Carbon Copy address(es), comma-separated."
- " Placeholders can be used here. "
- "e.g. ${object.email_cc}"),
- 'def_bcc':fields.char(
- 'BCC',
- size=250,
- help="Blind Carbon Copy address(es), comma-separated."
- " Placeholders can be used here. "
- "e.g. ${object.email_bcc}"),
- 'reply_to':fields.char('Reply-To',
- size=250,
- help="The address recipients should reply to,"
- " if different from the From address."
- " Placeholders can be used here. "
- "e.g. ${object.email_reply_to}"),
- 'message_id':fields.char('Message-ID',
- size=250,
- help="Specify the Message-ID SMTP header to use in outgoing emails. Please note that this overrides the Resource tracking option! Placeholders can be used here."),
- 'track_campaign_item':fields.boolean('Resource Tracking',
- help="Enable this is you wish to include a special \
-tracking marker in outgoing emails so you can identify replies and link \
-them back to the corresponding resource record. \
-This is useful for CRM leads for example"),
- 'lang':fields.char(
- 'Language',
- size=250,
- help="The default language for the email."
- " Placeholders can be used here. "
- "eg. ${object.partner_id.lang}"),
- 'def_subject':fields.char(
- 'Subject',
- size=200,
- help="The subject of email."
- " Placeholders can be used here.",
- translate=True),
- 'def_body_text':fields.text(
- 'Standard Body (Text)',
- help="The text version of the mail",
- translate=True),
- 'def_body_html':fields.text(
- 'Body (Text-Web Client Only)',
- help="The text version of the mail",
- translate=True),
- 'use_sign':fields.boolean(
- 'Signature',
- help="the signature from the User details"
- " will be appended to the mail"),
- 'file_name':fields.char(
- 'Report Filename',
- size=200,
- help="Name of the generated report file. Placeholders can be used in the filename. eg: 2009_SO003.pdf",
- translate=True),
- 'report_template':fields.many2one(
- 'ir.actions.report.xml',
- 'Report to send'),
- 'attachment_ids': fields.many2many(
- 'ir.attachment',
- 'email_template_attachment_rel',
- 'email_template_id',
- 'attachment_id',
- 'Attached Files',
- help="You may attach existing files to this template, "
- "so they will be added in all emails created from this template"),
- 'ref_ir_act_window':fields.many2one(
- 'ir.actions.act_window',
- 'Window Action',
- help="Action that will open this email template on Resource records",
- readonly=True),
- 'ref_ir_value':fields.many2one(
- 'ir.values',
- 'Wizard Button',
- help="Button in the side bar of the form view of this Resource that will invoke the Window Action",
- readonly=True),
- 'allowed_groups':fields.many2many(
- 'res.groups',
- 'template_group_rel',
- 'templ_id', 'group_id',
- string="Allowed User Groups",
- help="Only users from these groups will be"
- " allowed to send mails from this Template"),
- 'model_object_field':fields.many2one(
- 'ir.model.fields',
- string="Field",
- help="Select the field from the model you want to use."
- "\nIf it is a relationship field you will be able to "
- "choose the nested values in the box below\n(Note:If "
- "there are no values make sure you have selected the"
- " correct model)",
- store=False),
- 'sub_object':fields.many2one(
- 'ir.model',
- 'Sub-model',
- help='When a relation field is used this field'
- ' will show you the type of field you have selected',
- store=False),
- 'sub_model_object_field':fields.many2one(
- 'ir.model.fields',
- 'Sub Field',
- help="When you choose relationship fields "
- "this field will specify the sub value you can use.",
- store=False),
- 'null_value':fields.char(
- 'Null Value',
- help="This Value is used if the field is empty",
- size=50, store=False),
- 'copyvalue':fields.char(
- 'Expression',
- size=100,
- help="Copy and paste the value in the "
- "location you want to use a system value.",
- store=False),
- 'table_html':fields.text(
- 'HTML code',
- help="Copy this html code to your HTML message"
- " body for displaying the info in your mail.",
- store=False),
- #Template language(engine eg.Mako) specifics
- 'template_language':fields.selection(
- TEMPLATE_ENGINES,
- 'Templating Language',
- required=True
- )
+ 'name': fields.char('Name', size=250),
+ 'model_id': fields.many2one('ir.model', 'Related document model'),
+ 'lang': fields.char('Language Selection', size=250,
+ help="Optional translation language (ISO code) to select when sending out an email. "
+ "If not set, the english version will be used. "
+ "This should usually be a placeholder expression "
+ "that provides the appropriate language code, e.g. "
+ "${object.partner_id.lang.code}."),
+ 'user_signature': fields.boolean('Add Signature',
+ help="If checked, the user's signature will be appended to the text version "
+ "of the message"),
+ 'report_name': fields.char('Report Filename', size=200, translate=True,
+ help="Name to use for the generated report file (may contain placeholders)\n"
+ "The extension can be omitted and will then come from the report type."),
+ 'report_template':fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
+ 'ref_ir_act_window':fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True,
+ help="Sidebar action to make this template available on records "
+ "of the related document model"),
+ 'ref_ir_value':fields.many2one('ir.values', 'Sidebar button', readonly=True,
+ help="Sidebar button to open the sidebar action"),
+ 'track_campaign_item': fields.boolean('Resource Tracking',
+ help="Enable this is you wish to include a special tracking marker "
+ "in outgoing emails so you can identify replies and link "
+ "them back to the corresponding resource record. "
+ "This is useful for CRM leads for example"),
+
+ # Overridden mail.message.common fields for technical reasons:
+ 'model': fields.related('model_id','model', type='char', string='Related Document model',
+ size=128, select=True, store=True, readonly=True),
+ # we need a separate m2m table to avoid ID collisions with the original mail.message entries
+ 'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
+ 'attachment_id', 'Files to attach',
+ help="You may attach files to this template, to be added to all "
+ "emails created from this template"),
+
+ # Overridden mail.message.common fields to make tooltips more appropriate:
+ 'subject':fields.char('Subject', size=512, translate=True, help="Subject (placeholders may be used here)",),
+ 'email_from': fields.char('From', size=128, help="Sender address (placeholders may be used here)"),
+ 'email_to': fields.char('To', size=256, help="Comma-separated recipient addresses (placeholders may be used here)"),
+ 'email_cc': fields.char('Cc', size=256, help="Carbon copy recipients (placeholders may be used here)"),
+ 'email_bcc': fields.char('Bcc', size=256, help="Blind carbon copy recipients (placeholders may be used here)"),
+ 'reply_to': fields.char('Reply-To', size=250, help="Preferred response address (placeholders may be used here)"),
+ 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
+ help="Optional preferred server for outgoing mails. If not set, the highest "
+ "priority one will be used."),
+ 'body_text': fields.text('Text contents', translate=True, help="Plaintext version of the message (placeholders may be used here)"),
+ 'body_html': fields.text('Rich-text contents', help="Rich-text/HTML version of the message (placeholders may be used here)"),
+ 'message_id': fields.char('Message-Id', size=256, help="Message-ID SMTP header to use in outgoing messages based on this template. "
+ "Please note that this overrides the 'Resource Tracking' option, "
+ "so if you simply need to track replies to outgoing emails, enable "
+ "that option instead.\n"
+ "Placeholders must be used here, as this value always needs to be unique!"),
+
+ # Fake fields used to implement the placeholder assistant
+ 'model_object_field': fields.many2one('ir.model.fields', string="Field",
+ help="Select target field from the related document model.\n"
+ "If it is a relationship field you will be able to select "
+ "a target field at the destination of the relationship."),
+ 'sub_object': fields.many2one('ir.model', 'Sub-model', readonly=True,
+ help="When a relationship field is selected as first field, "
+ "this field shows the document model the relationship goes to."),
+ 'sub_model_object_field': fields.many2one('ir.model.fields', 'Sub-field',
+ help="When a relationship field is selected as first field, "
+ "this field lets you select the target field within the "
+ "destination document model (sub-model)."),
+ 'null_value': fields.char('Null value', help="Optional value to use if the target field is empty", size=128),
+ 'copyvalue': fields.char('Expression', size=256, help="Final placeholder expression, to be copy-pasted in the desired template field."),
}
_defaults = {
- 'template_language' : lambda *a:'mako',
-
+ 'track_campaign_item': True
}
- _sql_constraints = [
- ('name', 'unique (name)','The template name must be unique !')
- ]
-
def create_action(self, cr, uid, ids, context=None):
vals = {}
- if context is None:
- context = {}
- template_obj = self.browse(cr, uid, ids, context=context)[0]
- src_obj = template_obj.object_name.model
- vals['ref_ir_act_window'] = self.pool.get('ir.actions.act_window').create(cr, uid, {
- 'name': template_obj.name,
- 'type': 'ir.actions.act_window',
- 'res_model': 'email_template.send.wizard',
- 'src_model': src_obj,
- 'view_type': 'form',
- 'context': "{'src_model':'%s','template_id':'%d','src_rec_id':active_id,'src_rec_ids':active_ids}" % (src_obj, template_obj.id),
- 'view_mode':'form,tree',
- 'view_id': self.pool.get('ir.ui.view').search(cr, uid, [('name', '=', 'email_template.send.wizard.form')], context=context)[0],
- 'target': 'new',
- 'auto_refresh':1
- }, context)
- ir_values_obj = self.pool.get('ir.values')
- vals['ref_ir_value'] = ir_values_obj.create(cr, uid, {
- 'name': _('Send Mail (%s)') % template_obj.name,
- 'model': src_obj,
- 'key2': 'client_action_multi',
- 'value': "ir.actions.act_window," + str(vals['ref_ir_act_window']),
- 'object': True,
- }, context)
+ action_obj = self.pool.get('ir.actions.act_window')
+ data_obj = self.pool.get('ir.model.data')
+ for template in self.browse(cr, uid, ids, context=context):
+ src_obj = template.model_id.model
+ model_data_id = data_obj._get_id(cr, uid, 'mail', 'email_compose_message_wizard_form')
+ res_id = data_obj.browse(cr, uid, model_data_id, context=context).res_id
+ vals['ref_ir_act_window'] = action_obj.create(cr, uid, {
+ 'name': template.name,
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'mail.compose.message',
+ 'src_model': src_obj,
+ 'view_type': 'form',
+ 'context': "{'mail.compose.message.mode':'mass_mail'}",
+ 'view_mode':'form,tree',
+ 'view_id': res_id,
+ 'target': 'new',
+ 'auto_refresh':1
+ }, context)
+ vals['ref_ir_value'] = self.pool.get('ir.values').create(cr, uid, {
+ 'name': _('Send Mail (%s)') % template.name,
+ 'model': src_obj,
+ 'key2': 'client_action_multi',
+ 'value': "ir.actions.act_window," + str(vals['ref_ir_act_window']),
+ 'object': True,
+ }, context)
self.write(cr, uid, ids, {
- 'ref_ir_act_window': vals['ref_ir_act_window'],
- 'ref_ir_value': vals['ref_ir_value'],
- }, context)
+ 'ref_ir_act_window': vals.get('ref_ir_act_window',False),
+ 'ref_ir_value': vals.get('ref_ir_value',False),
+ }, context)
return True
def unlink_action(self, cr, uid, ids, context=None):
@@ -304,9 +213,6 @@ This is useful for CRM leads for example"),
ir_values_obj.unlink(cr, uid, template.ref_ir_value.id, context)
except:
raise osv.except_osv(_("Warning"), _("Deletion of Record failed"))
-
- def delete_action(self, cr, uid, ids, context=None):
- self.unlink_action(cr, uid, ids, context=context)
return True
def unlink(self, cr, uid, ids, context=None):
@@ -314,511 +220,167 @@ This is useful for CRM leads for example"),
return super(email_template, self).unlink(cr, uid, ids, context=context)
def copy(self, cr, uid, id, default=None, context=None):
+ template = self.browse(cr, uid, id, context=context)
if default is None:
default = {}
default = default.copy()
- old = self.read(cr, uid, id, ['name'], context=context)
- new_name = _("Copy of template %s") % old.get('name', 'No Name')
- check = self.search(cr, uid, [('name', '=', new_name)], context=context)
- if check:
- new_name = new_name + '_' + random.choice('abcdefghij') + random.choice('lmnopqrs') + random.choice('tuvwzyz')
- default.update({'name':new_name})
+ default['name'] = template.name + _('(copy)')
return super(email_template, self).copy(cr, uid, id, default, context)
- def build_expression(self, field_name, sub_field_name, null_value, template_language='mako'):
- """
- Returns a template expression based on data provided
- @param field_name: field name
- @param sub_field_name: sub field name (M2O)
- @param null_value: default value if the target value is empty
- @param template_language: name of template engine
- @return: computed expression
- """
+ def build_expression(self, field_name, sub_field_name, null_value):
+ """Returns a placeholder expression for use in a template field,
+ based on the values provided in the placeholder assistant.
+ :param field_name: main field name
+ :param sub_field_name: sub field name (M2O)
+ :param null_value: default value if the target value is empty
+ :return: final placeholder expression
+ """
expression = ''
- if template_language == 'mako':
- if field_name:
- expression = "${object." + field_name
- if sub_field_name:
- expression += "." + sub_field_name
- if null_value:
- expression += " or '''%s'''" % null_value
- expression += "}"
- elif template_language == 'django':
- if field_name:
- expression = "{{object." + field_name
- if sub_field_name:
- expression += "." + sub_field_name
- if null_value:
- expression += "|default: '''%s'''" % null_value
- expression += "}}"
+ if field_name:
+ expression = "${object." + field_name
+ if sub_field_name:
+ expression += "." + sub_field_name
+ if null_value:
+ expression += " or '''%s'''" % null_value
+ expression += "}"
return expression
- def onchange_model_object_field(self, cr, uid, ids, model_object_field, template_language, context=None):
- if not model_object_field:
- return {}
- result = {}
- field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
- #Check if field is relational
- if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
- res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
- if res_ids:
- result['sub_object'] = res_ids[0]
- result['copyvalue'] = self.build_expression(False,
- False,
- False,
- template_language)
- result['sub_model_object_field'] = False
- result['null_value'] = False
- else:
- #Its a simple field... just compute placeholder
- result['sub_object'] = False
- result['copyvalue'] = self.build_expression(field_obj.name,
- False,
- False,
- template_language
- )
- result['sub_model_object_field'] = False
- result['null_value'] = False
+ def onchange_sub_model_object_value_field(self, cr, uid, ids, model_object_field, sub_model_object_field=False, null_value=None, context=None):
+ result = {
+ 'sub_object': False,
+ 'copyvalue': False,
+ 'sub_model_object_field': False,
+ 'null_value': False
+ }
+ if model_object_field:
+ fields_obj = self.pool.get('ir.model.fields')
+ field_value = fields_obj.browse(cr, uid, model_object_field, context)
+ if field_value.ttype in ['many2one', 'one2many', 'many2many']:
+ res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_value.relation)], context=context)
+ sub_field_value = False
+ if sub_model_object_field:
+ sub_field_value = fields_obj.browse(cr, uid, sub_model_object_field, context)
+ if res_ids:
+ result.update({
+ 'sub_object': res_ids[0],
+ 'copyvalue': self.build_expression(field_value.name, sub_field_value and sub_field_value.name or False, null_value or False),
+ 'sub_model_object_field': sub_model_object_field or False,
+ 'null_value': null_value or False
+ })
+ else:
+ result.update({
+ 'copyvalue': self.build_expression(field_value.name, False, null_value or False),
+ 'null_value': null_value or False
+ })
return {'value':result}
- def onchange_sub_model_object_field(self, cr, uid, ids, model_object_field, sub_model_object_field, template_language, context=None):
- if not model_object_field or not sub_model_object_field:
- return {}
- result = {}
- field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
- if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
- res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
- sub_field_obj = self.pool.get('ir.model.fields').browse(cr, uid, sub_model_object_field, context)
- if res_ids:
- result['sub_object'] = res_ids[0]
- result['copyvalue'] = self.build_expression(field_obj.name,
- sub_field_obj.name,
- False,
- template_language
- )
- result['sub_model_object_field'] = sub_model_object_field
- result['null_value'] = False
- else:
- #Its a simple field... just compute placeholder
- result['sub_object'] = False
- result['copyvalue'] = self.build_expression(field_obj.name,
- False,
- False,
- template_language
- )
- result['sub_model_object_field'] = False
- result['null_value'] = False
- return {'value':result}
- def onchange_null_value(self, cr, uid, ids, model_object_field, sub_model_object_field, null_value, template_language, context=None):
- if not model_object_field and not null_value:
- return {}
- result = {}
- field_obj = self.pool.get('ir.model.fields').browse(cr, uid, model_object_field, context)
- if field_obj.ttype in ['many2one', 'one2many', 'many2many']:
- res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_obj.relation)], context=context)
- sub_field_obj = self.pool.get('ir.model.fields').browse(cr, uid, sub_model_object_field, context)
- if res_ids:
- result['sub_object'] = res_ids[0]
- result['copyvalue'] = self.build_expression(field_obj.name,
- sub_field_obj.name,
- null_value,
- template_language
- )
- result['sub_model_object_field'] = sub_model_object_field
- result['null_value'] = null_value
- else:
- #Its a simple field... just compute placeholder
- result['sub_object'] = False
- result['copyvalue'] = self.build_expression(field_obj.name,
- False,
- null_value,
- template_language
- )
- result['sub_model_object_field'] = False
- result['null_value'] = null_value
- return {'value':result}
+ def generate_email(self, cr, uid, template_id, res_id, context=None):
+ """Generates an email from the template for given (model, res_id) pair.
- def _add_attachment(self, cursor, user, mailbox_id, name, data, filename, context=None):
+ :param template_id: id of the template to render.
+ :param res_id: id of the record to use for rendering the template (model
+ is taken from template definition)
+ :returns: a dict containing all relevant fields for creating a new
+ mail.message entry, with the addition one additional
+ special key ``attachments`` containing a list of
"""
- Add an attachment to a given mailbox entry.
-
- :param data: base64 encoded attachment data to store
- """
- attachment_obj = self.pool.get('ir.attachment')
- attachment_data = {
- 'name': (name or '') + _(' (Email Attachment)'),
- 'datas': data,
- 'datas_fname': filename,
- 'description': name or _('No Description'),
- 'res_model':'email_template.mailbox',
- 'res_id': mailbox_id,
+ if context is None:
+ context = {}
+ values = {
+ 'subject': False,
+ 'body_text': False,
+ 'body_html': False,
+ 'email_from': False,
+ 'email_to': False,
+ 'email_cc': False,
+ 'email_bcc': False,
+ 'reply_to': False,
+ 'auto_delete': False,
+ 'model': False,
+ 'res_id': False,
+ 'mail_server_id': False,
+ 'attachments': False,
+ 'attachment_ids': False,
+ 'message_id': False,
+ 'state': 'outgoing',
}
- attachment_id = attachment_obj.create(cursor,
- user,
- attachment_data,
- context)
- if attachment_id:
- self.pool.get('email_template.mailbox').write(
- cursor,
- user,
- mailbox_id,
- {
- 'attachments_ids':[(4, attachment_id)],
- 'mail_type':'multipart/mixed'
- },
- context)
+ if not template_id:
+ return values
- def generate_attach_reports(self,
- cursor,
- user,
- template,
- record_id,
- mail,
- context=None):
- """
- Generate report to be attached and attach it
- to the email, and add any directly attached files as well.
+ report_xml_pool = self.pool.get('ir.actions.report.xml')
+ template = self.get_email_template(cr, uid, template_id, res_id, context)
- @param cursor: Database Cursor
- @param user: ID of User
- @param template: Browse record of
- template
- @param record_id: ID of the target model
- for which this mail has
- to be generated
- @param mail: Browse record of email object
- @return: True
- """
+ for field in ['subject', 'body_text', 'body_html', 'email_from',
+ 'email_to', 'email_cc', 'email_bcc', 'reply_to',
+ 'message_id']:
+ values[field] = self.render_template(cr, uid, getattr(template, field),
+ template.model, res_id, context=context) \
+ or False
+
+ if template.user_signature:
+ signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
+ values['body_text'] += '\n\n' + signature
+
+ values.update(mail_server_id = template.mail_server_id.id or False,
+ auto_delete = template.auto_delete,
+ model=template.model,
+ res_id=res_id or False)
+
+ attachments = {}
+ # Add report as a Document
if template.report_template:
- reportname = 'report.' + \
- self.pool.get('ir.actions.report.xml').read(
- cursor,
- user,
- template.report_template.id,
- ['report_name'],
- context)['report_name']
- service = netsvc.LocalService(reportname)
- data = {}
- data['model'] = template.model_int_name
- (result, format) = service.create(cursor,
- user,
- [record_id],
- data,
- context)
- fname = tools.ustr(get_value(cursor, user, record_id,
- template.file_name, template, context)
- or 'Report')
- ext = '.' + format
- if not fname.endswith(ext):
- fname += ext
- self._add_attachment(cursor, user, mail.id, mail.subject, base64.b64encode(result), fname, context)
-
- if template.attachment_ids:
- for attachment in template.attachment_ids:
- self._add_attachment(cursor, user, mail.id, attachment.name, attachment.datas, attachment.datas_fname, context)
-
- return True
-
- def _generate_mailbox_item_from_template(self,
- cursor,
- user,
- template,
- record_id,
- context=None):
- """
- Generates an email from the template for
- record record_id of target object
-
- @param cursor: Database Cursor
- @param user: ID of User
- @param template: Browse record of
- template
- @param record_id: ID of the target model
- for which this mail has
- to be generated
- @return: ID of created object
- """
- if context is None:
- context = {}
- #If account to send from is in context select it, else use enforced account
- if 'account_id' in context.keys():
- from_account = self.pool.get('email_template.account').read(
- cursor,
- user,
- context.get('account_id'),
- ['name', 'email_id'],
- context
- )
- else:
- from_account = {
- 'id':template.from_account.id,
- 'name':template.from_account.name,
- 'email_id':template.from_account.email_id
- }
- lang = get_value(cursor,
- user,
- record_id,
- template.lang,
- template,
- context)
- if lang:
+ report_name = template.report_name
+ report_service = 'report.' + report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
+ # Ensure report is rendered using template's language
ctx = context.copy()
- ctx.update({'lang':lang})
- template = self.browse(cursor, user, template.id, context=ctx)
+ if template.lang:
+ ctx['lang'] = self.render_template(cr, uid, template.lang, template.model, res_id, context)
+ service = netsvc.LocalService(report_service)
+ (result, format) = service.create(cr, uid, [res_id], {'model': template.model}, ctx)
+ result = base64.b64encode(result)
+ if not report_name:
+ report_name = report_service
+ ext = "." + format
+ if not report_name.endswith(ext):
+ report_name += ext
+ attachments[report_name] = result
- # determine name of sender, either it is specified in email_id or we
- # use the account name
- email_id = from_account['email_id'].strip()
- email_from = re.findall(r'([^ ,<@]+@[^> ,]+)', email_id)[0]
- if email_from != email_id:
- # we should keep it all, name is probably specified in the address
- email_from = from_account['email_id']
- else:
- email_from = tools.ustr(from_account['name']) + "<" + tools.ustr(email_id) + ">"
+ # Add document attachments
+ for attach in template.attachment_ids:
+ # keep the bytes as fetched from the db, base64 encoded
+ attachments[attach.datas_fname] = attach.datas
- # FIXME: should do this in a loop and rename template fields to the corresponding
- # mailbox fields. (makes no sense to have different names I think.
- mailbox_values = {
- 'email_from': email_from,
- 'email_to':get_value(cursor,
- user,
- record_id,
- template.def_to,
- template,
- context),
- 'email_cc':get_value(cursor,
- user,
- record_id,
- template.def_cc,
- template,
- context),
- 'email_bcc':get_value(cursor,
- user,
- record_id,
- template.def_bcc,
- template,
- context),
- 'reply_to':get_value(cursor,
- user,
- record_id,
- template.reply_to,
- template,
- context),
- 'subject':get_value(cursor,
- user,
- record_id,
- template.def_subject,
- template,
- context),
- 'body_text':get_value(cursor,
- user,
- record_id,
- template.def_body_text,
- template,
- context),
- 'body_html':get_value(cursor,
- user,
- record_id,
- template.def_body_html,
- template,
- context),
- 'account_id' :from_account['id'],
- #This is a mandatory field when automatic emails are sent
- 'state':'na',
- 'folder':'drafts',
- 'mail_type':'multipart/alternative',
- }
+ values['attachments'] = attachments
+ return values
- if template['message_id']:
- # use provided message_id with placeholders
- mailbox_values.update({'message_id': get_value(cursor, user, record_id, template['message_id'], template, context)})
+ def send_mail(self, cr, uid, template_id, res_id, context=None):
+ """Generates a new mail message for the given template and record,
+ and schedule it for delivery through the ``mail`` module's scheduler.
- elif template['track_campaign_item']:
- # get appropriate message-id
- mailbox_values.update({'message_id': tools.misc.generate_tracking_message_id(record_id)})
-
- if not mailbox_values['account_id']:
- raise Exception("Unable to send the mail. No account linked to the template.")
- #Use signatures if allowed
- if template.use_sign:
- sign = self.pool.get('res.users').read(cursor,
- user,
- user,
- ['signature'],
- context)['signature']
- if mailbox_values['body_text']:
- mailbox_values['body_text'] += sign
- if mailbox_values['body_html']:
- mailbox_values['body_html'] += sign
- mailbox_id = self.pool.get('email_template.mailbox').create(
- cursor,
- user,
- mailbox_values,
- context)
-
- return mailbox_id
-
-
- def generate_mail(self,
- cursor,
- user,
- template_id,
- record_ids,
- context=None):
- if context is None:
- context = {}
- template = self.browse(cursor, user, template_id, context=context)
- if not template:
- raise Exception("The requested template could not be loaded")
- result = True
- mailbox_obj = self.pool.get('email_template.mailbox')
- for record_id in record_ids:
- mailbox_id = self._generate_mailbox_item_from_template(
- cursor,
- user,
- template,
- record_id,
- context)
- mail = mailbox_obj.browse(
- cursor,
- user,
- mailbox_id,
- context=context
- )
- if template.report_template or template.attachment_ids:
- self.generate_attach_reports(
- cursor,
- user,
- template,
- record_id,
- mail,
- context
- )
-
- self.pool.get('email_template.mailbox').write(
- cursor,
- user,
- mailbox_id,
- {'folder':'outbox'},
- context=context
- )
- # TODO : manage return value of all the records
- result = self.pool.get('email_template.mailbox').send_this_mail(cursor, user, [mailbox_id], context)
- return result
-
-email_template()
-
-
-## FIXME: this class duplicates a lot of features of the email template send wizard,
-## one of the 2 should inherit from the other!
-
-class email_template_preview(osv.osv_memory):
- _name = "email_template.preview"
- _description = "Email Template Preview"
-
- def _get_model_recs(self, cr, uid, context=None):
- if context is None:
- context = {}
- #Fills up the selection box which allows records from the selected object to be displayed
- self.context = context
- if 'template_id' in context:
- ref_obj_id = self.pool.get('email.template').read(cr, uid, context['template_id'], ['object_name'], context)
- ref_obj_name = self.pool.get('ir.model').read(cr, uid, ref_obj_id['object_name'][0], ['model'], context)['model']
- model_obj = self.pool.get(ref_obj_name)
- ref_obj_ids = model_obj.search(cr, uid, [], 0, 20, 'id', context=context)
- if not ref_obj_ids:
- ref_obj_ids = []
-
- # also add the default one if requested, otherwise it won't be available for selection:
- default_id = context.get('default_rel_model_ref')
- if default_id and default_id not in ref_obj_ids:
- ref_obj_ids.insert(0, default_id)
- return model_obj.name_get(cr, uid, ref_obj_ids, context)
- return []
-
- def default_get(self, cr, uid, fields, context=None):
- if context is None:
- context = {}
- result = super(email_template_preview, self).default_get(cr, uid, fields, context=context)
- if (not fields or 'rel_model_ref' in fields) and 'template_id' in context \
- and not result.get('rel_model_ref'):
- selectables = self._get_model_recs(cr, uid, context=context)
- result['rel_model_ref'] = selectables and selectables[0][0] or False
- return result
-
- def _default_model(self, cursor, user, context=None):
+ :param int template_id: id of the template to render
+ :param int res_id: id of the record to render the template with
+ (model is taken from the template)
"""
- Returns the default value for model field
- @param cursor: Database Cursor
- @param user: ID of current user
- @param context: OpenERP Context
- """
- return self.pool.get('email.template').read(
- cursor,
- user,
- context['template_id'],
- ['object_name'],
- context).get('object_name', False)
-
- _columns = {
- 'ref_template':fields.many2one(
- 'email.template',
- 'Template', readonly=True),
- 'rel_model':fields.many2one('ir.model', 'Model', readonly=True),
- 'rel_model_ref':fields.selection(_get_model_recs, 'Referred Document'),
- 'to':fields.char('To', size=250, readonly=True),
- 'cc':fields.char('CC', size=250, readonly=True),
- 'bcc':fields.char('BCC', size=250, readonly=True),
- 'reply_to':fields.char('Reply-To',
- size=250,
- help="The address recipients should reply to,"
- " if different from the From address."
- " Placeholders can be used here."),
- 'message_id':fields.char('Message-ID',
- size=250,
- help="The Message-ID header value, if you need to"
- "specify it, for example to automatically recognize the replies later."
- " Placeholders can be used here."),
- 'subject':fields.char('Subject', size=200, readonly=True),
- 'body_text':fields.text('Body', readonly=True),
- 'body_html':fields.text('Body', readonly=True),
- 'report':fields.char('Report Name', size=100, readonly=True),
- }
- _defaults = {
- 'ref_template': lambda self, cr, uid, ctx:ctx['template_id'] or False,
- 'rel_model': _default_model,
- }
- def on_change_ref(self, cr, uid, ids, rel_model_ref, context=None):
- if context is None:
- context = {}
- if not rel_model_ref:
- return {}
- vals = {}
- if context == {}:
- context = self.context
- template = self.pool.get('email.template').browse(cr, uid, context['template_id'], context)
- #Search translated template
- lang = get_value(cr, uid, rel_model_ref, template.lang, template, context)
- if lang:
- ctx = context.copy()
- ctx.update({'lang':lang})
- template = self.pool.get('email.template').browse(cr, uid, context['template_id'], ctx)
- vals['to'] = get_value(cr, uid, rel_model_ref, template.def_to, template, context)
- vals['cc'] = get_value(cr, uid, rel_model_ref, template.def_cc, template, context)
- vals['bcc'] = get_value(cr, uid, rel_model_ref, template.def_bcc, template, context)
- vals['reply_to'] = get_value(cr, uid, rel_model_ref, template.reply_to, template, context)
- if template.message_id:
- vals['message_id'] = get_value(cr, uid, rel_model_ref, template.message_id, template, context)
- elif template.track_campaign_item:
- vals['message_id'] = tools.misc.generate_tracking_message_id(rel_model_ref)
- vals['subject'] = get_value(cr, uid, rel_model_ref, template.def_subject, template, context)
- vals['body_text'] = get_value(cr, uid, rel_model_ref, template.def_body_text, template, context)
- vals['body_html'] = get_value(cr, uid, rel_model_ref, template.def_body_html, template, context)
- vals['report'] = get_value(cr, uid, rel_model_ref, template.file_name, template, context)
- return {'value':vals}
-
-email_template_preview()
+ mail_message = self.pool.get('mail.message')
+ ir_attachment = self.pool.get('ir.attachment')
+ template = self.browse(cr, uid, template_id, context)
+ values = self.generate_email(cr, uid, template_id, res_id, context=context)
+ attachments = values.pop('attachments') or {}
+ message_id = mail_message.create(cr, uid, values, context=context)
+ # link attachments
+ attachment_ids = []
+ for fname, fcontent in attachments.iteritems():
+ attachment_data = {
+ 'name': fname,
+ 'datas_fname': fname,
+ 'datas': fcontent,
+ 'res_model': mail_message._name,
+ 'res_id': message_id,
+ }
+ if context.has_key('default_type'):
+ del context['default_type']
+ attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context))
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/email_template/email_template_account.py b/addons/email_template/email_template_account.py
deleted file mode 100644
index a5f51400c8a..00000000000
--- a/addons/email_template/email_template_account.py
+++ /dev/null
@@ -1,470 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2009 Sharoon Thomas
-# Copyright (C) 2004-2010 OpenERP SA ()
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
-#
-##############################################################################
-
-from osv import osv, fields
-import re
-import smtplib
-import base64
-from email import Encoders
-from email.mime.base import MIMEBase
-from email.mime.multipart import MIMEMultipart
-from email.mime.text import MIMEText
-from email.header import decode_header, Header
-from email.utils import formatdate
-import netsvc
-import datetime
-from tools.translate import _
-import tools
-import logging
-
-EMAIL_PATTERN = re.compile(r'([^()\[\] ,<:\\>@";]+@[^()\[\] ,<:\\>@";]+)') # See RFC822
-def extract_emails(emails_str):
- """
- Returns a list of email addresses recognized in a string, ignoring the rest of the string.
- extract_emails('a@b.com,c@bcom, "John Doe" , e@b.com') -> ['a@b.com','c@bcom', 'd@b.com', 'e@b.com']"
- """
- return EMAIL_PATTERN.findall(emails_str)
-
-
-def extract_emails_from_dict(addresses={}):
- """
- Extracts email addresses from a dictionary with comma-separated address string values, handling
- separately the To, CC, BCC and Reply-To addresses.
-
- :param addresses: a dictionary of addresses in the form {'To': 'a@b.com,c@bcom; d@b.com;e@b.com' , 'CC': 'e@b.com;f@b.com', ... }
- :return: a dictionary with a list of separate addresses for each header (To, CC, BCC), with an additional key 'all-recipients'
- containing all addresses for the 'To', 'CC', 'BCC' entries.
- """
- result = {'all-recipients':[]}
- keys = ['To', 'CC', 'BCC', 'Reply-To']
- for each in keys:
- emails = extract_emails(addresses.get(each, u''))
- while u'' in emails:
- emails.remove(u'')
- result[each] = emails
- if each != 'Reply-To':
- result['all-recipients'].extend(emails)
- return result
-
-class email_template_account(osv.osv):
- """
- Object to store email account settings
- """
- _name = "email_template.account"
- _known_content_types = ['multipart/mixed',
- 'multipart/alternative',
- 'multipart/related',
- 'text/plain',
- 'text/html'
- ]
- _columns = {
- 'name': fields.char('Description',
- size=64, required=True,
- readonly=True, select=True,
- help="The description is used as the Sender name along with the provided From Email, \
-unless it is already specified in the From Email, e.g: John Doe ",
- states={'draft':[('readonly', False)]}),
- 'auto_delete': fields.boolean('Auto Delete', size=64, readonly=True,
- help="Permanently delete emails after sending",
- states={'draft':[('readonly', False)]}),
- 'user':fields.many2one('res.users',
- 'Related User', required=True,
- readonly=True, states={'draft':[('readonly', False)]}),
- 'email_id': fields.char('From Email',
- size=120, required=True,
- readonly=True, states={'draft':[('readonly', False)]} ,
- help="eg: 'john@doe.com' or 'John Doe '"),
- 'smtpserver': fields.char('Server',
- size=120, required=True,
- readonly=True, states={'draft':[('readonly', False)]},
- help="Enter name of outgoing server, eg: smtp.yourdomain.com"),
- 'smtpport': fields.integer('SMTP Port',
- size=64, required=True,
- readonly=True, states={'draft':[('readonly', False)]},
- help="Enter port number, eg: 25 or 587"),
- 'smtpuname': fields.char('User Name',
- size=120, required=False,
- readonly=True, states={'draft':[('readonly', False)]},
- help="Specify the username if your SMTP server requires authentication, "
- "otherwise leave it empty."),
- 'smtppass': fields.char('Password',
- size=120, invisible=True,
- required=False, readonly=True,
- states={'draft':[('readonly', False)]}),
- 'smtptls':fields.boolean('TLS',
- states={'draft':[('readonly', False)]}, readonly=True),
-
- 'smtpssl':fields.boolean('SSL/TLS (only in python 2.6)',
- states={'draft':[('readonly', False)]}, readonly=True),
- 'send_pref':fields.selection([
- ('html', 'HTML, otherwise Text'),
- ('text', 'Text, otherwise HTML'),
- ('alternative', 'Both HTML & Text (Alternative)'),
- ('mixed', 'Both HTML & Text (Mixed)')
- ], 'Mail Format', required=True),
- 'company':fields.selection([
- ('yes', 'Yes'),
- ('no', 'No')
- ], 'Corporate',
- readonly=True,
- help="Select if this mail account does not belong " \
- "to specific user but to the organization as a whole. " \
- "eg: info@companydomain.com",
- required=True, states={
- 'draft':[('readonly', False)]
- }),
-
- 'state':fields.selection([
- ('draft', 'Initiated'),
- ('suspended', 'Suspended'),
- ('approved', 'Approved')
- ],
- 'State', required=True, readonly=True),
- }
-
- _defaults = {
- 'name':lambda self, cursor, user, context:self.pool.get(
- 'res.users'
- ).read(
- cursor,
- user,
- user,
- ['name'],
- context
- )['name'],
- 'state':lambda * a:'draft',
- 'smtpport':lambda *a:25,
- 'smtpserver':lambda *a:'localhost',
- 'company':lambda *a:'yes',
- 'user':lambda self, cursor, user, context:user,
- 'send_pref':lambda *a: 'html',
- 'smtptls':lambda *a:True,
- }
-
- _sql_constraints = [
- (
- 'email_uniq',
- 'unique (email_id)',
- 'Another setting already exists with this email ID !')
- ]
-
- def name_get(self, cr, uid, ids, context=None):
- return [(a["id"], "%s (%s)" % (a['email_id'], a['name'])) for a in self.read(cr, uid, ids, ['name', 'email_id'], context=context)]
-
- def _constraint_unique(self, cursor, user, ids, context=None):
- """
- This makes sure that you dont give personal
- users two accounts with same ID (Validated in sql constaints)
- However this constraint exempts company accounts.
- Any no of co accounts for a user is allowed
- """
- if self.read(cursor, user, ids, ['company'])[0]['company'] == 'no':
- accounts = self.search(cursor, user, [
- ('user', '=', user),
- ('company', '=', 'no')
- ])
- if len(accounts) > 1 :
- return False
- else :
- return True
- else:
- return True
-
- _constraints = [
- (_constraint_unique,
- 'Error: You are not allowed to have more than 1 account.',
- [])
- ]
-
- def get_outgoing_server(self, cursor, user, ids, context=None):
- """
- Returns the Out Going Connection (SMTP) object
-
- @attention: DO NOT USE except_osv IN THIS METHOD
- @param cursor: Database Cursor
- @param user: ID of current user
- @param ids: ID/list of ids of current object for
- which connection is required
- First ID will be chosen from lists
- @param context: Context
-
- @return: SMTP server object or Exception
- """
- #Type cast ids to integer
- if type(ids) == list:
- ids = ids[0]
- this_object = self.browse(cursor, user, ids, context=context)
- if this_object:
- if this_object.smtpserver and this_object.smtpport:
- try:
- if this_object.smtpssl:
- serv = smtplib.SMTP_SSL(this_object.smtpserver, this_object.smtpport)
- else:
- serv = smtplib.SMTP(this_object.smtpserver, this_object.smtpport)
- if this_object.smtptls:
- serv.ehlo()
- serv.starttls()
- serv.ehlo()
- except Exception, error:
- raise error
- try:
- if serv.has_extn('AUTH') or this_object.smtpuname or this_object.smtppass:
- serv.login(str(this_object.smtpuname), str(this_object.smtppass))
- except Exception, error:
- raise error
- return serv
- raise Exception(_("SMTP SERVER or PORT not specified"))
- raise Exception(_("Core connection for the given ID does not exist"))
-
- def check_outgoing_connection(self, cursor, user, ids, context=None):
- """
- checks SMTP credentials and confirms if outgoing connection works
- (Attached to button)
- @param cursor: Database Cursor
- @param user: ID of current user
- @param ids: list of ids of current object for
- which connection is required
- @param context: Context
- """
- try:
- self.get_outgoing_server(cursor, user, ids, context)
- raise osv.except_osv(_("SMTP Test Connection Was Successful"), '')
- except osv.except_osv, success_message:
- raise success_message
- except Exception, error:
- raise osv.except_osv(
- _("Out going connection test failed"),
- _("Reason: %s") % tools.ustr(error)
- )
-
- def do_approval(self, cr, uid, ids, context=None):
- #TODO: Check if user has rights
- self.write(cr, uid, ids, {'state':'approved'}, context=context)
-# wf_service = netsvc.LocalService("workflow")
-
- def smtp_connection(self, cursor, user, id, context=None):
- """
- This method should now wrap smtp_connection
- """
- #This function returns a SMTP server object
- logger = netsvc.Logger()
- core_obj = self.browse(cursor, user, id, context=context)
- if core_obj.smtpserver and core_obj.smtpport and core_obj.state == 'approved':
- try:
- serv = self.get_outgoing_server(cursor, user, id, context)
- except Exception, error:
- logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed on login. Probable Reason:Could not login to server\nError: %s") % (id, tools.ustr(error)))
- return False
- #Everything is complete, now return the connection
- return serv
- else:
- logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id)
- return False
-
-#**************************** MAIL SENDING FEATURES ***********************#
-
-
-
-
- def send_mail(self, cr, uid, ids, addresses, subject='', body=None, payload=None, message_id=None, context=None):
- #TODO: Replace all this with a single email object
- if body is None:
- body = {}
- if payload is None:
- payload = {}
- if context is None:
- context = {}
- logger = netsvc.Logger()
- for id in ids:
- core_obj = self.browse(cr, uid, id, context)
- serv = self.smtp_connection(cr, uid, id)
- if serv:
- try:
- # Prepare multipart containers depending on data
- text_subtype = (core_obj.send_pref == 'alternative') and 'alternative' or 'mixed'
- # Need a multipart/mixed wrapper for attachments if content is alternative
- if payload and text_subtype == 'alternative':
- payload_part = MIMEMultipart(_subtype='mixed')
- text_part = MIMEMultipart(_subtype=text_subtype)
- payload_part.attach(text_part)
- else:
- # otherwise a single multipart/mixed will do the whole job
- payload_part = text_part = MIMEMultipart(_subtype=text_subtype)
-
- if subject:
- payload_part['Subject'] = subject
- from_email = core_obj.email_id
- if '<' in from_email:
- # We have a structured email address, keep it untouched
- payload_part['From'] = Header(core_obj.email_id, 'utf-8').encode()
- else:
- # Plain email address, construct a structured one based on the name:
- sender_name = Header(core_obj.name, 'utf-8').encode()
- payload_part['From'] = sender_name + " <" + core_obj.email_id + ">"
- payload_part['Organization'] = tools.ustr(core_obj.user.company_id.name)
- payload_part['Date'] = formatdate()
- addresses_l = extract_emails_from_dict(addresses)
- if addresses_l['To']:
- payload_part['To'] = u','.join(addresses_l['To'])
- if addresses_l['CC']:
- payload_part['CC'] = u','.join(addresses_l['CC'])
- if addresses_l['Reply-To']:
- payload_part['Reply-To'] = addresses_l['Reply-To'][0]
- if message_id:
- payload_part['Message-ID'] = message_id
- if body.get('text', False):
- temp_body_text = body.get('text', '')
- l = len(temp_body_text.replace(' ', '').replace('\r', '').replace('\n', ''))
- if l == 0:
- body['text'] = u'No Mail Message'
- # Attach parts into message container.
- # According to RFC 2046, the last part of a multipart message, in this case
- # the HTML message, is best and preferred.
- if core_obj.send_pref in ('text', 'mixed', 'alternative'):
- body_text = body.get('text', u'')
- body_text = tools.ustr(body_text)
- text_part.attach(MIMEText(body_text.encode("utf-8"), _charset='UTF-8'))
- if core_obj.send_pref in ('html', 'mixed', 'alternative'):
- html_body = body.get('html', u'')
- if len(html_body) == 0 or html_body == u'':
- html_body = body.get('text', u'
<Empty Message>
').replace('\n', ' ').replace('\r', ' ')
- html_body = tools.ustr(html_body)
- text_part.attach(MIMEText(html_body.encode("utf-8"), _subtype='html', _charset='UTF-8'))
-
- #Now add attachments if any, wrapping into a container multipart/mixed if needed
- if payload:
- for file in payload:
- part = MIMEBase('application', "octet-stream")
- part.set_payload(base64.decodestring(payload[file]))
- part.add_header('Content-Disposition', 'attachment; filename="%s"' % file)
- Encoders.encode_base64(part)
- payload_part.attach(part)
- except Exception, error:
- logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:MIME Error\nDescription: %s") % (id, tools.ustr(error)))
- return {'error_msg': _("Server Send Error\nDescription: %s")%error}
- try:
- serv.sendmail(payload_part['From'], addresses_l['all-recipients'], payload_part.as_string())
- except Exception, error:
- logging.getLogger('email_template').error(_("Mail from Account %s failed. Probable Reason: Server Send Error\n Description: %s"), id, tools.ustr(error), exc_info=True)
- return {'error_msg': _("Server Send Error\nDescription: %s") % tools.ustr(error)}
- #The mail sending is complete
- serv.close()
- logger.notifyChannel(_("Email Template"), netsvc.LOG_INFO, _("Mail from Account %s successfully Sent.") % (id))
- return True
- else:
- logger.notifyChannel(_("Email Template"), netsvc.LOG_ERROR, _("Mail from Account %s failed. Probable Reason:Account not approved") % id)
- return {'nodestroy':True,'error_msg': _("Mail from Account %s failed. Probable Reason:Account not approved")% id}
-
- def extracttime(self, time_as_string):
- """
- TODO: DOC THis
- """
- logger = netsvc.Logger()
- #The standard email dates are of format similar to:
- #Thu, 8 Oct 2009 09:35:42 +0200
- date_as_date = False
- convertor = {'+':1, '-':-1}
- try:
- time_as_string = time_as_string.replace(',', '')
- date_list = time_as_string.split(' ')
- date_temp_str = ' '.join(date_list[1:5])
- if len(date_list) >= 6:
- sign = convertor.get(date_list[5][0], False)
- else:
- sign = False
- try:
- dt = datetime.datetime.strptime(
- date_temp_str,
- "%d %b %Y %H:%M:%S")
- except:
- try:
- dt = datetime.datetime.strptime(
- date_temp_str,
- "%d %b %Y %H:%M")
- except:
- return False
- if sign:
- try:
- offset = datetime.timedelta(
- hours=sign * int(
- date_list[5][1:3]
- ),
- minutes=sign * int(
- date_list[5][3:5]
- )
- )
- except Exception, e2:
- """Looks like UT or GMT, just forget decoding"""
- return False
- else:
- offset = datetime.timedelta(hours=0)
- dt = dt + offset
- date_as_date = dt.strftime('%Y-%m-%d %H:%M:%S')
- except Exception, e:
- logger.notifyChannel(
- _("Email Template"),
- netsvc.LOG_WARNING,
- _(
- "Datetime Extraction failed.Date:%s \
- \tError:%s") % (
- time_as_string,
- tools.ustr(e))
- )
- return date_as_date
-
- def send_receive(self, cr, uid, ids, context=None):
- for id in ids:
- ctx = context.copy()
- ctx['filters'] = [('account_id', '=', id)]
- self.pool.get('email_template.mailbox').send_all_mail(cr, uid, [], context=ctx)
- return True
-
- def decode_header_text(self, text):
- """ Decode internationalized headers RFC2822.
- To, CC, BCC, Subject fields can contain
- text slices with different encodes, like:
- =?iso-8859-1?Q?Enric_Mart=ED?= ,
- =?Windows-1252?Q?David_G=F3mez?=
- Sometimes they include extra " character at the beginning/
- end of the contact name, like:
- "=?iso-8859-1?Q?Enric_Mart=ED?="
- and decode_header() does not work well, so we use regular
- expressions (?= ? ? ?=) to split the text slices
- """
- if not text:
- return text
- p = re.compile("(=\?.*?\?.\?.*?\?=)")
- text2 = ''
- try:
- for t2 in p.split(text):
- text2 += ''.join(
- [s.decode(
- t or 'ascii'
- ) for (s, t) in decode_header(t2)]
- ).encode('utf-8')
- except:
- return text
- return text2
-
-email_template_account()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/email_template/email_template_account_view.xml b/addons/email_template/email_template_account_view.xml
deleted file mode 100644
index 09f9062d187..00000000000
--- a/addons/email_template/email_template_account_view.xml
+++ /dev/null
@@ -1,124 +0,0 @@
-
-
-
-
-
-
-
-
-
- email_template.account.form
- email_template.account
- form
-
-
-
-
-
-
-
- email_template.mailbox.tree
- email_template.mailbox
- tree
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- email_template.mailbox.search
- email_template.mailbox
- search
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Emails
- email_template.mailbox
- form
- tree,form
-
- {'search_default_outbox': 1}
- An email template is an email document that will be sent as part of a marketing campaign. You can personalize it according to specific customer profile fields, so that a partner name or other partner related information may be inserted automatically.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/email_template/email_template_view.xml b/addons/email_template/email_template_view.xml
index 7d2a5f7ad5c..d6a651f0bc2 100644
--- a/addons/email_template/email_template_view.xml
+++ b/addons/email_template/email_template_view.xml
@@ -1,141 +1,99 @@
-
-
-
- email_template.preview.form
- email_template.preview
- form
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Template Preview
- email_template.preview
- email_template.preview
- ir.actions.act_window
- form
- form
-
- new
- {'template_id':active_id}
-
-
email.template.formemail.templateform
-
+
+
+
+
+
+
+
+
+
+
+
-
@@ -353,7 +378,7 @@
search
-
+
@@ -364,7 +389,7 @@
-
+
@@ -415,6 +440,19 @@
+
+ hr.salary.rule.category.tree.view
+ hr.salary.rule.category
+ tree
+ children_ids
+
+
+
+
+
+
+
+ hr.salary.rule.category.select
@@ -422,8 +460,8 @@
search
-
-
+
+
@@ -441,6 +479,19 @@
parent="payroll_configure"
sequence="11"
/>
+
+ Salary Rule Categories Hierarchy
+ hr.salary.rule.category
+ tree
+
+
+
+
+ hr.payslip.run.search.inherit
+ hr.payslip.run
+ search
+
+
+
+
+
+
+
+
+
+
+ hr.payslip.run.tree.inherit
+ hr.payslip.run
+ tree
+
+
+
+
+
+
+
+
+
+ hr.payslip.run.form.inherit
+ hr.payslip.run
+ form
+
+
+
+
+
+
+
+
+
diff --git a/addons/hr_payroll_account/wizard/__init__.py b/addons/hr_payroll_account/wizard/__init__.py
new file mode 100644
index 00000000000..27128f05597
--- /dev/null
+++ b/addons/hr_payroll_account/wizard/__init__.py
@@ -0,0 +1,25 @@
+#-*- coding:utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved
+# d$
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import hr_payroll_payslips_by_employees
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
diff --git a/addons/hr_payroll_account/wizard/hr_payroll_payslips_by_employees.py b/addons/hr_payroll_account/wizard/hr_payroll_payslips_by_employees.py
new file mode 100644
index 00000000000..a00ff97ef6b
--- /dev/null
+++ b/addons/hr_payroll_account/wizard/hr_payroll_payslips_by_employees.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from osv import osv
+
+class hr_payslip_employees(osv.osv_memory):
+
+ _inherit ='hr.payslip.employees'
+
+ def compute_sheet(self, cr, uid, ids, context=None):
+ run_pool = self.pool.get('hr.payslip.run')
+ if context is None:
+ context = {}
+ if context and context.get('active_id', False):
+ run_data = run_pool.read(cr, uid, context['active_id'], ['journal_id'])
+ journal_id = run_data.get('journal_id', False)[0]
+ context.update({'journal_id': journal_id})
+ return super(hr_payslip_employees, self).compute_sheet(cr, uid, ids, context=context)
+
+hr_payslip_employees()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py
index 3456f9c8bbe..9ad7180441c 100644
--- a/addons/hr_recruitment/hr_recruitment.py
+++ b/addons/hr_recruitment/hr_recruitment.py
@@ -29,6 +29,9 @@ import collections
import binascii
import tools
from tools.translate import _
+from crm import wizard
+
+wizard.mail_compose_message.SUPPORTED_MODELS.append('hr.applicant')
AVAILABLE_STATES = [
('draft', 'New'),
@@ -84,13 +87,16 @@ class hr_recruitment_degree(osv.osv):
_defaults = {
'sequence': 1,
}
+ _sql_constraints = [
+ ('name_uniq', 'unique (name)', 'The name of the Degree of Recruitment must be unique!')
+ ]
hr_recruitment_degree()
class hr_applicant(crm.crm_case, osv.osv):
_name = "hr.applicant"
_description = "Applicant"
_order = "id desc"
- _inherit = ['mailgate.thread']
+ _inherit = ['mail.thread']
def _compute_day(self, cr, uid, ids, fields, args, context=None):
"""
@@ -128,7 +134,7 @@ class hr_applicant(crm.crm_case, osv.osv):
_columns = {
'name': fields.char('Name', size=128, required=True),
- 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."),
'description': fields.text('Description'),
'email_from': fields.char('Email', size=128, help="These people will receive email."),
@@ -279,7 +285,6 @@ class hr_applicant(crm.crm_case, osv.osv):
id3 = data_obj.browse(cr, uid, id3, context=context).res_id
context = {
- 'default_opportunity_id': opp.id,
'default_partner_id': opp.partner_id and opp.partner_id.id or False,
'default_email_from': opp.email_from,
'default_state': 'open',
@@ -319,22 +324,13 @@ class hr_applicant(crm.crm_case, osv.osv):
value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context)
return value
- def message_new(self, cr, uid, msg, context=None):
- """
- Automatically calls when new email message arrives
-
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks
- """
- mailgate_pool = self.pool.get('email.server.tools')
- attach_obj = self.pool.get('ir.attachment')
-
+ def message_new(self, cr, uid, msg, custom_values=None, context=None):
+ """Automatically called when new email message arrives"""
+ res_id = super(hr_applicant,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
subject = msg.get('subject') or _("No Subject")
- body = msg.get('body')
+ body = msg.get('body_text')
msg_from = msg.get('from')
priority = msg.get('priority')
-
vals = {
'name': subject,
'email_from': msg_from,
@@ -342,42 +338,19 @@ class hr_applicant(crm.crm_case, osv.osv):
'description': body,
'user_id': False,
}
- if msg.get('priority', False):
+ if priority:
vals['priority'] = priority
+ vals.update(self.message_partner_by_email(cr, uid, msg.get('from', False)))
+ self.write(cr, uid, [res_id], vals, context)
+ return res_id
- res = mailgate_pool.get_partner(cr, uid, msg.get('from'))
- if res:
- vals.update(res)
- res = self.create(cr, uid, vals, context=context)
-
- attachents = msg.get('attachments', [])
- for attactment in attachents or []:
- data_attach = {
- 'name': attactment,
- 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
- 'datas_fname': attactment,
- 'description': 'Mail attachment',
- 'res_model': self._name,
- 'res_id': res,
- }
- attach_obj.create(cr, uid, data_attach, context=context)
-
- return res
-
- def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
- """
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of update mail’s IDs
- """
-
+ def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None):
if isinstance(ids, (str, int, long)):
ids = [ids]
msg_from = msg['from']
vals.update({
- 'description': msg['body']
+ 'description': msg['body_text']
})
if msg.get('priority', False):
vals['priority'] = msg.get('priority')
@@ -388,7 +361,7 @@ class hr_applicant(crm.crm_case, osv.osv):
'probability':'probability'
}
vls = { }
- for line in msg['body'].split('\n'):
+ for line in msg['body_text'].split('\n'):
line = line.strip()
res = tools.misc.command_re.match(line)
if res and maps.get(res.group(1).lower(), False):
@@ -397,19 +370,9 @@ class hr_applicant(crm.crm_case, osv.osv):
vals.update(vls)
res = self.write(cr, uid, ids, vals, context=context)
+ self.message_append_dict(cr, uid, ids, msg, context=context)
return res
- def msg_send(self, cr, uid, id, *args, **argv):
- """ Send The Message
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of email’s IDs
- @param *args: Return Tuple Value
- @param **args: Return Dictionary of Keyword Value
- """
- return True
-
def case_open(self, cr, uid, ids, *args):
"""
@param self: The object pointer
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml
index b1d957aa6dc..fce529731fc 100644
--- a/addons/hr_recruitment/hr_recruitment_view.xml
+++ b/addons/hr_recruitment/hr_recruitment_view.xml
@@ -144,14 +144,14 @@
-
+
-
+
@@ -160,21 +160,19 @@
-
-
+
-
-
-
+
+
+
-
-
+
+
@@ -183,14 +181,13 @@
-
+ name="%(mail.action_email_compose_message_wizard)d"
+ icon="terp-mail-message-new" type="action"/>
@@ -247,10 +244,10 @@
-
+
-
+
diff --git a/addons/hr_recruitment/report/hr_recruitment_report_view.xml b/addons/hr_recruitment/report/hr_recruitment_report_view.xml
index 4f39005a3d1..5353a8497ed 100644
--- a/addons/hr_recruitment/report/hr_recruitment_report_view.xml
+++ b/addons/hr_recruitment/report/hr_recruitment_report_view.xml
@@ -59,7 +59,6 @@
help="Recruitment performed in current month"/>
diff --git a/addons/hr_recruitment/security/hr_recruitment_security.xml b/addons/hr_recruitment/security/hr_recruitment_security.xml
index e159be6a9db..570b0f036cd 100644
--- a/addons/hr_recruitment/security/hr_recruitment_security.xml
+++ b/addons/hr_recruitment/security/hr_recruitment_security.xml
@@ -2,6 +2,12 @@
+
+ Applicant multi company rule
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
diff --git a/addons/hr_recruitment/security/ir.model.access.csv b/addons/hr_recruitment/security/ir.model.access.csv
index 2907b96fd81..96baaf9e278 100644
--- a/addons/hr_recruitment/security/ir.model.access.csv
+++ b/addons/hr_recruitment/security/ir.model.access.csv
@@ -3,7 +3,7 @@
"access_hr_recruitment_report","hr.recruitment.report","model_hr_recruitment_report","base.group_hr_manager",1,1,1,1
"access_hr_recruitment_stage_user","hr.recruitment.stage.user","model_hr_recruitment_stage","base.group_hr_user",1,1,1,1
"access_hr_recruitment_degree","hr.recruitment.degree","model_hr_recruitment_degree","base.group_hr_user",1,1,1,1
-"access_mailgate_message_user","mailgate.message.user","mail_gateway.model_mailgate_message","base.group_hr_user",1,1,1,1
+"access_mail_message_user","mail.message.user","mail.model_mail_message","base.group_hr_user",1,1,1,1
"access_res_partner_hr_user","res.partner.user","base.model_res_partner","base.group_hr_user",1,1,1,1
"access_res_partner_address_hr_user","res.partner.address.user","base.model_res_partner_address","base.group_hr_user",1,1,1,1
"access_survey_hr_user","survey.hr.user","survey.model_survey","base.group_hr_user",1,1,1,0
diff --git a/addons/hr_recruitment/wizard/hr_recruitment_phonecall.py b/addons/hr_recruitment/wizard/hr_recruitment_phonecall.py
index edd08b5b531..cd9cbea52f7 100644
--- a/addons/hr_recruitment/wizard/hr_recruitment_phonecall.py
+++ b/addons/hr_recruitment/wizard/hr_recruitment_phonecall.py
@@ -32,7 +32,7 @@ class job2phonecall(osv.osv_memory):
'deadline': fields.datetime('Planned Date'),
'note': fields.text('Goals'),
'category_id': fields.many2one('crm.case.categ', 'Category', required=True),
- }
+ }
def _date_user(self, cr, uid, context=None):
case_obj = self.pool.get('hr.applicant')
@@ -50,7 +50,7 @@ class job2phonecall(osv.osv_memory):
return categ_id and categ_id[0] or case.categ_id and case.categ_id.id or False
def _get_note(self, cr, uid, context=None):
- msg_obj = self.pool.get('mailgate.message')
+ mail_message = self.pool.get('mail.message')
if context is None:
context = {}
if context.get('active_id'):
@@ -58,9 +58,9 @@ class job2phonecall(osv.osv_memory):
if case.description:
return case.description
else:
- msg_ids = msg_obj.search(cr, uid, [('model', '=', 'hr.applicant'), ('res_id', '=', case.id), ('email_from', '!=', ''), ('email_to', '!=', '')], limit=1)
+ msg_ids = mail_message.search(cr, uid, [('model', '=', 'hr.applicant'), ('res_id', '=', case.id), ('email_from', '!=', ''), ('email_to', '!=', '')], limit=1)
if msg_ids:
- return msg_obj.browse(cr, uid, msg_ids[0], context=context).description
+ return mail_message.browse(cr, uid, msg_ids[0], context=context).body_text
return False
diff --git a/addons/hr_timesheet/hr_timesheet_view.xml b/addons/hr_timesheet/hr_timesheet_view.xml
index 2092773fad9..710291c8f32 100644
--- a/addons/hr_timesheet/hr_timesheet_view.xml
+++ b/addons/hr_timesheet/hr_timesheet_view.xml
@@ -62,7 +62,7 @@
search
-
+
diff --git a/addons/hr_timesheet/i18n/cs.po b/addons/hr_timesheet/i18n/cs.po
index c6dd897c9a0..089a96b1d85 100644
--- a/addons/hr_timesheet/i18n/cs.po
+++ b/addons/hr_timesheet/i18n/cs.po
@@ -4,45 +4,46 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: OpenERP Server 5.0.4\n"
+"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:15+0000\n"
-"PO-Revision-Date: 2009-02-03 20:59+0000\n"
-"Last-Translator: Fabien (Open ERP) \n"
-"Language-Team: \n"
+"PO-Revision-Date: 2011-09-20 08:48+0000\n"
+"Last-Translator: Jiří Hajda \n"
+"Language-Team: Czech \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-09-05 05:24+0000\n"
-"X-Generator: Launchpad (build 13830)\n"
+"X-Launchpad-Export-Date: 2011-09-21 04:50+0000\n"
+"X-Generator: Launchpad (build 13996)\n"
+"X-Poedit-Language: Czech\n"
#. module: hr_timesheet
#: model:product.template,name:hr_timesheet.product_consultant_product_template
msgid "Service on Timesheet"
-msgstr ""
+msgstr "SLužby v časovém rozvrhu"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:43
#: code:addons/hr_timesheet/report/users_timesheet.py:77
#, python-format
msgid "Wed"
-msgstr ""
+msgstr "St"
#. module: hr_timesheet
#: view:hr.sign.out.project:0
msgid "(Keep empty for current_time)"
-msgstr ""
+msgstr "(Nechejte prázdné pro aktuální čas)"
#. module: hr_timesheet
#: code:addons/hr_timesheet/wizard/hr_timesheet_sign_in_out.py:132
#, python-format
msgid "No employee defined for your user !"
-msgstr ""
+msgstr "Pro vašeho uživatele nebyl určen zaměstnanec !"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
msgid "Group By..."
-msgstr ""
+msgstr "Seskupit podle..."
#. module: hr_timesheet
#: model:ir.actions.act_window,help:hr_timesheet.action_hr_timesheet_sign_in
@@ -52,16 +53,20 @@ msgid ""
"analytic account. This feature allows to record at the same time the "
"attendance and the timesheet."
msgstr ""
+"Zaměstnanci mohou vkládat své časy strávené na různých projektech. Projekt "
+"je analytický účet a čas strávený na projektu generuje ceny na na "
+"analytickém účtu. Tato funkce umožňuje zaznamenat ve stejný čas docházku a "
+"časový rozvrh."
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
msgid "Today"
-msgstr ""
+msgstr "Dnes"
#. module: hr_timesheet
#: field:hr.employee,journal_id:0
msgid "Analytic Journal"
-msgstr ""
+msgstr "Analytický deník"
#. module: hr_timesheet
#: view:hr.sign.out.project:0
@@ -72,31 +77,31 @@ msgstr "Ukončit práci"
#: model:ir.actions.act_window,name:hr_timesheet.action_hr_timesheet_employee
#: model:ir.ui.menu,name:hr_timesheet.menu_hr_timesheet_employee
msgid "Employee Timesheet"
-msgstr ""
+msgstr "Časový rozvrh zaměstnance"
#. module: hr_timesheet
#: view:account.analytic.account:0
msgid "Work done stats"
-msgstr ""
+msgstr "Statistika dokončené práce"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
#: model:ir.ui.menu,name:hr_timesheet.menu_hr_reporting_timesheet
msgid "Timesheet"
-msgstr ""
+msgstr "Časový rozvrh"
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "janvier"
-msgstr ""
+msgstr "leden"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:43
#: code:addons/hr_timesheet/report/users_timesheet.py:77
#, python-format
msgid "Mon"
-msgstr ""
+msgstr "Po"
#. module: hr_timesheet
#: view:hr.sign.in.project:0
@@ -111,27 +116,31 @@ msgid ""
"project generates costs on the analytic account. This feature allows to "
"record at the same time the attendance and the timesheet."
msgstr ""
+"Zaměstnanci mohou vkládat své časy strávené na různých projektech, ke kterým "
+"jsou přiřazeni. Projekt je analytický účet a čas strávený na projektu "
+"generuje ceny na na analytickém účtu. Tato funkce umožňuje zaznamenat ve "
+"stejný čas docházku a časový rozvrh."
#. module: hr_timesheet
#: field:hr.sign.out.project,analytic_amount:0
msgid "Minimum Analytic Amount"
-msgstr ""
+msgstr "Minimální analytická částka"
#. module: hr_timesheet
#: view:hr.analytical.timesheet.employee:0
msgid "Monthly Employee Timesheet"
-msgstr ""
+msgstr "Měsíční časový rozvrh zaměstnance"
#. module: hr_timesheet
#: view:hr.sign.out.project:0
msgid "Work done in the last period"
-msgstr ""
+msgstr "Dokončená práce v minulém období"
#. module: hr_timesheet
#: constraint:hr.employee:0
msgid ""
"Error ! You cannot select a department for which the employee is the manager."
-msgstr ""
+msgstr "Chyba ! Nemůžete vybrat oddělení, kterého je zaměstnance vedoucí."
#. module: hr_timesheet
#: field:hr.sign.in.project,state:0
@@ -143,39 +152,39 @@ msgstr "Současné datum"
#: field:hr.sign.in.project,name:0
#: field:hr.sign.out.project,name:0
msgid "Employees name"
-msgstr ""
+msgstr "Jméno zaměstnance"
#. module: hr_timesheet
#: model:ir.model,name:hr_timesheet.model_hr_analytical_timesheet_users
msgid "Print Employees Timesheet"
-msgstr ""
+msgstr "Tisknou časový rozvrh zaměstnance"
#. module: hr_timesheet
#: code:addons/hr_timesheet/hr_timesheet.py:174
#: code:addons/hr_timesheet/hr_timesheet.py:176
#, python-format
msgid "Warning !"
-msgstr ""
+msgstr "Varování !"
#. module: hr_timesheet
#: code:addons/hr_timesheet/wizard/hr_timesheet_sign_in_out.py:77
#: code:addons/hr_timesheet/wizard/hr_timesheet_sign_in_out.py:132
#, python-format
msgid "UserError"
-msgstr ""
+msgstr "UserError"
#. module: hr_timesheet
#: code:addons/hr_timesheet/wizard/hr_timesheet_sign_in_out.py:77
#, python-format
msgid "No cost unit defined for this employee !"
-msgstr ""
+msgstr "Pro tohoto zaměstnance nebyla určena cenová jednotka !"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:43
#: code:addons/hr_timesheet/report/users_timesheet.py:77
#, python-format
msgid "Tue"
-msgstr ""
+msgstr "Út"
#. module: hr_timesheet
#: field:hr.sign.out.project,account_id:0
@@ -186,32 +195,32 @@ msgstr "Analytický účet"
#: code:addons/hr_timesheet/wizard/hr_timesheet_print_employee.py:42
#, python-format
msgid "Warning"
-msgstr ""
+msgstr "Varování"
#. module: hr_timesheet
#: model:ir.module.module,shortdesc:hr_timesheet.module_meta_information
msgid "Human Resources (Timesheet encoding)"
-msgstr ""
+msgstr "Lidské zdroje (Kódování časových rozvrhů)"
#. module: hr_timesheet
#: view:hr.sign.in.project:0
#: view:hr.sign.out.project:0
msgid "Sign In/Out By Project"
-msgstr ""
+msgstr "Přihlášení/Odhlášení podle projektu"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:43
#: code:addons/hr_timesheet/report/users_timesheet.py:77
#, python-format
msgid "Sat"
-msgstr ""
+msgstr "So"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:43
#: code:addons/hr_timesheet/report/users_timesheet.py:77
#, python-format
msgid "Sun"
-msgstr ""
+msgstr "Ne"
#. module: hr_timesheet
#: view:hr.analytical.timesheet.employee:0
@@ -222,18 +231,18 @@ msgstr "Tisk"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
msgid "Timesheet Lines"
-msgstr ""
+msgstr "Řádky časového rozvrhu"
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "juillet"
-msgstr ""
+msgstr "červenec"
#. module: hr_timesheet
#: view:hr.analytical.timesheet.users:0
msgid "Monthly Employees Timesheet"
-msgstr ""
+msgstr "Měsíční časový rozvrh zaměstnance"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -242,7 +251,7 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "July"
-msgstr ""
+msgstr "Červenec"
#. module: hr_timesheet
#: field:hr.sign.in.project,date:0
@@ -253,13 +262,13 @@ msgstr "Počáteční datum"
#. module: hr_timesheet
#: view:hr.employee:0
msgid "Categories"
-msgstr ""
+msgstr "Kategorie"
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "novembre"
-msgstr ""
+msgstr "listopad"
#. module: hr_timesheet
#: model:ir.actions.act_window,help:hr_timesheet.act_hr_timesheet_line_evry1_all_form
@@ -267,6 +276,8 @@ msgid ""
"Through Working Hours you can register your working hours by project every "
"day."
msgstr ""
+"Pomocí pracovních hodin můžete každý den registrovat vaše pracovní hodiny "
+"podle projektu."
#. module: hr_timesheet
#: model:ir.module.module,description:hr_timesheet.module_meta_information
@@ -283,6 +294,18 @@ msgid ""
"to set up a management by affair.\n"
" "
msgstr ""
+"\n"
+"Tento modul realizuje systém časového rozvrhu. Každý zaměstnanec může "
+"zadávat a\n"
+"sledovat svůj čas strávený na různých projektech. Projekt je \n"
+"analytický účet a čas strávený na projektu generuje ceny na\n"
+"analytickém účtu.\n"
+"\n"
+"Je poskytováno mnoho vykazování nad časem a sledováním zaměstnanců.\n"
+"\n"
+"Je celkově integrováno s module účtování cen. Umožňuje vám\n"
+"nastavit správu podle poměru.\n"
+" "
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -291,18 +314,18 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "March"
-msgstr ""
+msgstr "Březen"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
msgid "Total cost"
-msgstr ""
+msgstr "Celková cena"
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "décembre"
-msgstr ""
+msgstr "prosinec"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -311,22 +334,22 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "September"
-msgstr ""
+msgstr "Září"
#. module: hr_timesheet
#: model:ir.model,name:hr_timesheet.model_hr_analytic_timesheet
msgid "Timesheet Line"
-msgstr ""
+msgstr "Řádek časového rozvrhu"
#. module: hr_timesheet
#: field:hr.analytical.timesheet.users,employee_ids:0
msgid "employees"
-msgstr ""
+msgstr "zaměstnanci"
#. module: hr_timesheet
#: view:account.analytic.account:0
msgid "Stats by month"
-msgstr ""
+msgstr "Statistika podle měsíce"
#. module: hr_timesheet
#: view:account.analytic.account:0
@@ -343,25 +366,25 @@ msgstr "Popis práce"
#. module: hr_timesheet
#: view:account.analytic.account:0
msgid "To be invoiced"
-msgstr ""
+msgstr "K fakturaci"
#. module: hr_timesheet
#: model:ir.actions.report.xml,name:hr_timesheet.report_user_timesheet
msgid "Employee timesheet"
-msgstr ""
+msgstr "Časový rozvrh zaměstnance"
#. module: hr_timesheet
#: model:ir.actions.act_window,name:hr_timesheet.action_hr_timesheet_sign_in
#: model:ir.actions.act_window,name:hr_timesheet.action_hr_timesheet_sign_out
msgid "Sign in / Sign out by project"
-msgstr ""
+msgstr "Přihlášení / Odhlášení podle projektu"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:43
#: code:addons/hr_timesheet/report/users_timesheet.py:77
#, python-format
msgid "Fri"
-msgstr ""
+msgstr "Pá"
#. module: hr_timesheet
#: view:hr.sign.in.project:0
@@ -375,27 +398,29 @@ msgid ""
"Analytic journal is not defined for employee %s \n"
"Define an employee for the selected user and assign an analytic journal!"
msgstr ""
+"Pro zaměstnance %s není určen Analytický deník \n"
+"Určete zaměstnance pro vybraného uživatele a přiřaďte analxtický deník!"
#. module: hr_timesheet
#: view:hr.sign.in.project:0
msgid "(Keep empty for current time)"
-msgstr ""
+msgstr "(Nechejte prázdné pro aktuální čas)"
#. module: hr_timesheet
#: view:hr.employee:0
msgid "Timesheets"
-msgstr ""
+msgstr "Časové rozvrhy"
#. module: hr_timesheet
#: help:hr.employee,product_id:0
msgid "Specifies employee's designation as a product with type 'service'."
-msgstr ""
+msgstr "Určuje zařazení zaměstnance jako výrobek s typem 'služba'."
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "août"
-msgstr ""
+msgstr "srpen"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -404,7 +429,7 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "August"
-msgstr ""
+msgstr "Srpen"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -413,23 +438,23 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "June"
-msgstr ""
+msgstr "Červen"
#. module: hr_timesheet
#: view:hr.analytical.timesheet.employee:0
msgid "Print My Timesheet"
-msgstr ""
+msgstr "Tisknout můj časový rozvrh"
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "mars"
-msgstr ""
+msgstr "březen"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
msgid "Date"
-msgstr ""
+msgstr "Datum"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -438,12 +463,12 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "November"
-msgstr ""
+msgstr "Listopad"
#. module: hr_timesheet
#: constraint:hr.employee:0
msgid "Error ! You cannot create recursive Hierarchy of Employees."
-msgstr ""
+msgstr "Chyba ! Nemůžete vytvořit rekurzivní Hierarchii zaměstnanců"
#. module: hr_timesheet
#: field:hr.sign.out.project,date:0
@@ -457,7 +482,7 @@ msgstr "Koncové datum"
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "October"
-msgstr ""
+msgstr "Říjen"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -466,35 +491,35 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "January"
-msgstr ""
+msgstr "Leden"
#. module: hr_timesheet
#: view:account.analytic.account:0
msgid "Key dates"
-msgstr ""
+msgstr "Klíčové dny"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:43
#: code:addons/hr_timesheet/report/users_timesheet.py:77
#, python-format
msgid "Thu"
-msgstr ""
+msgstr "Čt"
#. module: hr_timesheet
#: view:account.analytic.account:0
msgid "Analysis stats"
-msgstr ""
+msgstr "Statistiká analýza"
#. module: hr_timesheet
#: model:ir.model,name:hr_timesheet.model_hr_analytical_timesheet_employee
msgid "Print Employee Timesheet & Print My Timesheet"
-msgstr ""
+msgstr "Tisknout Časový rozvrh zaměstnance & Tisknou můj časový rozvrh"
#. module: hr_timesheet
#: field:hr.sign.in.project,emp_id:0
#: field:hr.sign.out.project,emp_id:0
msgid "Employee ID"
-msgstr ""
+msgstr "ID zaměstnance"
#. module: hr_timesheet
#: view:hr.sign.out.project:0
@@ -504,12 +529,12 @@ msgstr "Obecné informace"
#. module: hr_timesheet
#: model:ir.actions.act_window,name:hr_timesheet.action_hr_timesheet_my
msgid "My Timesheet"
-msgstr ""
+msgstr "Moje časové rozvrhy"
#. module: hr_timesheet
#: view:account.analytic.account:0
msgid "Analysis summary"
-msgstr ""
+msgstr "Souhrn analýzy"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -518,7 +543,7 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "December"
-msgstr ""
+msgstr "Prosinec"
#. module: hr_timesheet
#: view:hr.analytical.timesheet.employee:0
@@ -526,7 +551,7 @@ msgstr ""
#: view:hr.sign.in.project:0
#: view:hr.sign.out.project:0
msgid "Cancel"
-msgstr "Storno"
+msgstr "Zrušit"
#. module: hr_timesheet
#: model:ir.actions.act_window,name:hr_timesheet.action_hr_timesheet_users
@@ -534,30 +559,30 @@ msgstr "Storno"
#: model:ir.actions.wizard,name:hr_timesheet.wizard_hr_timesheet_users
#: model:ir.ui.menu,name:hr_timesheet.menu_hr_timesheet_users
msgid "Employees Timesheet"
-msgstr ""
+msgstr "Časové rozvrhy zaměstnanců"
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "février"
-msgstr ""
+msgstr "únor"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
msgid "Information"
-msgstr ""
+msgstr "Informace"
#. module: hr_timesheet
#: field:hr.analytical.timesheet.employee,employee_id:0
#: model:ir.model,name:hr_timesheet.model_hr_employee
msgid "Employee"
-msgstr ""
+msgstr "Zaměstnanec"
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "avril"
-msgstr ""
+msgstr "duben"
#. module: hr_timesheet
#: field:hr.sign.in.project,server_date:0
@@ -568,23 +593,23 @@ msgstr "Současné datum"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
msgid "Anlytic account"
-msgstr ""
+msgstr "Analytický účet"
#. module: hr_timesheet
#: view:hr.analytical.timesheet.employee:0
msgid "This wizard will print monthly timesheet"
-msgstr ""
+msgstr "Tento průvodce bude tisknout měsíční časový rozvrh"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
#: field:hr.employee,product_id:0
msgid "Product"
-msgstr ""
+msgstr "Výrobek"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
msgid "Invoicing"
-msgstr ""
+msgstr "Fakturace"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -593,34 +618,34 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "May"
-msgstr ""
+msgstr "Květen"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
msgid "Total time"
-msgstr ""
+msgstr "Celkový čas"
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "juin"
-msgstr ""
+msgstr "červen"
#. module: hr_timesheet
#: view:hr.sign.in.project:0
msgid "(local time on the server side)"
-msgstr "(lokální čas na serveru)"
+msgstr "(místní čas na straně serveru)"
#. module: hr_timesheet
#: model:ir.actions.act_window,name:hr_timesheet.act_hr_timesheet_line_evry1_all_form
#: model:ir.ui.menu,name:hr_timesheet.menu_hr_working_hours
msgid "Working Hours"
-msgstr ""
+msgstr "Pracovní hodiny"
#. module: hr_timesheet
#: model:ir.model,name:hr_timesheet.model_hr_sign_in_project
msgid "Sign In By Project"
-msgstr ""
+msgstr "Přihlášení podle projektu"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -629,28 +654,28 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "February"
-msgstr ""
+msgstr "Únor"
#. module: hr_timesheet
#: field:hr.analytic.timesheet,line_id:0
msgid "Analytic line"
-msgstr ""
+msgstr "Analytický řádek"
#. module: hr_timesheet
#: model:ir.model,name:hr_timesheet.model_hr_sign_out_project
msgid "Sign Out By Project"
-msgstr ""
+msgstr "Odhlášení podle projektu"
#. module: hr_timesheet
#: view:hr.analytical.timesheet.users:0
msgid "Employees"
-msgstr ""
+msgstr "Zaměstnanci"
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "octobre"
-msgstr ""
+msgstr "říjen"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40
@@ -659,7 +684,7 @@ msgstr ""
#: selection:hr.analytical.timesheet.users,month:0
#, python-format
msgid "April"
-msgstr ""
+msgstr "Duben"
#. module: hr_timesheet
#: code:addons/hr_timesheet/hr_timesheet.py:176
@@ -668,12 +693,14 @@ msgid ""
"No analytic account defined on the project.\n"
"Please set one or we can not automatically fill the timesheet."
msgstr ""
+"U projektu nebyl určen analytický účet.\n"
+"Prosíme nastavte jej nebo nemůžeme automaticky vyplnit časový rozvrh."
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "mai"
-msgstr ""
+msgstr "květen"
#. module: hr_timesheet
#: view:account.analytic.account:0
@@ -689,13 +716,13 @@ msgstr "Začít pracovat"
#. module: hr_timesheet
#: view:account.analytic.account:0
msgid "Stats by user"
-msgstr ""
+msgstr "Statistiky podle uživatele"
#. module: hr_timesheet
#: code:addons/hr_timesheet/wizard/hr_timesheet_print_employee.py:42
#, python-format
msgid "No employee defined for this user"
-msgstr ""
+msgstr "Pro tohoto uživatele nebyl určen zaměstnanec"
#. module: hr_timesheet
#: field:hr.analytical.timesheet.employee,year:0
@@ -706,23 +733,23 @@ msgstr "Rok"
#. module: hr_timesheet
#: view:hr.analytic.timesheet:0
msgid "Accounting"
-msgstr ""
+msgstr "Účetnictví"
#. module: hr_timesheet
#: field:hr.analytic.timesheet,partner_id:0
msgid "Partner Id"
-msgstr ""
+msgstr "Id partnera"
#. module: hr_timesheet
#: view:hr.sign.out.project:0
msgid "Change Work"
-msgstr ""
+msgstr "Změnit práci"
#. module: hr_timesheet
#: selection:hr.analytical.timesheet.employee,month:0
#: selection:hr.analytical.timesheet.users,month:0
msgid "septembre"
-msgstr ""
+msgstr "září"
#~ msgid "Choose Users"
#~ msgstr "Uživatelé"
diff --git a/addons/hr_timesheet/report/user_timesheet.py b/addons/hr_timesheet/report/user_timesheet.py
index 737652fbccd..533989210d6 100644
--- a/addons/hr_timesheet/report/user_timesheet.py
+++ b/addons/hr_timesheet/report/user_timesheet.py
@@ -67,7 +67,7 @@ class report_custom(report_rml):
if user_id:
# Computing the attendence by analytical account
cr.execute(
- "select line.date, (unit_amount * unit.factor) as amount, account_id, account.name "\
+ "select line.date, (unit_amount / unit.factor) as amount, account_id, account.name "\
"from account_analytic_line as line, hr_analytic_timesheet as hr, "\
"account_analytic_account as account, product_uom as unit "\
"where hr.line_id=line.id and line.account_id=account.id "\
diff --git a/addons/hr_timesheet/report/users_timesheet.py b/addons/hr_timesheet/report/users_timesheet.py
index fac92d6a322..4c619721329 100644
--- a/addons/hr_timesheet/report/users_timesheet.py
+++ b/addons/hr_timesheet/report/users_timesheet.py
@@ -37,7 +37,7 @@ def lengthmonth(year, month):
def emp_create_xml(cr, id, som, eom, emp):
# Computing the attendence by analytical account
cr.execute(
- "select line.date, (unit_amount * unit.factor) as amount "\
+ "select line.date, (unit_amount / unit.factor) as amount "\
"from account_analytic_line as line, hr_analytic_timesheet as hr, "\
"product_uom as unit "\
"where hr.line_id=line.id "\
diff --git a/addons/hr_timesheet/security/ir.model.access.csv b/addons/hr_timesheet/security/ir.model.access.csv
index 3cf9505b60b..2b2064a89b4 100644
--- a/addons/hr_timesheet/security/ir.model.access.csv
+++ b/addons/hr_timesheet/security/ir.model.access.csv
@@ -10,4 +10,5 @@
"access_account_fiscalyear_hr_user","account.account.fiscalyear.user","account.model_account_fiscalyear","base.group_hr_user",1,1,1,1
"hr.access_account_journal_view_hruser","account.journal.view hruser","account.model_account_journal_view","base.group_hr_user",1,0,0,0
"hr.access_account_journal_column_hruser","account.journal.column hruser","account.model_account_journal_column","base.group_hr_user",1,0,0,0
-"access_hr_analytic_timesheet_user","hr.analytic.timesheet.user","model_hr_analytic_timesheet","base.group_user",1,0,0,0
+"access_hr_analytic_timesheet_user","hr.analytic.timesheet.user","model_hr_analytic_timesheet","base.group_user",1,1,1,0
+"access_account_analytic_journal_user","account.account.analytic.journal.user","account.model_account_analytic_journal","base.group_user",1,0,0,0
diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
index d9314690ce7..87fb6064a04 100644
--- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
+++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
@@ -116,7 +116,7 @@ class account_analytic_line(osv.osv):
for line in self.browse(cr, uid, select):
if line.invoice_id:
raise osv.except_osv(_('Error !'),
- _('You can not modify an invoiced analytic line!'))
+ _('You cannot modify an invoiced analytic line!'))
return True
def copy(self, cursor, user, obj_id, default=None, context=None):
diff --git a/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report_view.xml b/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report_view.xml
index daeabfdaeee..e4226feb2f3 100644
--- a/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report_view.xml
+++ b/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report_view.xml
@@ -46,7 +46,7 @@
search
-
+
@@ -56,7 +56,6 @@
help="Timesheet lines in this month"/>
@@ -70,7 +69,7 @@
-
+
@@ -162,14 +161,14 @@
search
-
+
-
-
-
+
+
+
@@ -218,9 +217,9 @@
-
-
-
+
+
+
@@ -298,15 +297,15 @@
search
-
+
-
-
-
-
+
+
+
+
@@ -369,18 +368,18 @@
report_timesheet.accountsearch
-
-
+
+
-
-
-
-
+
+
+
+
-
+
diff --git a/addons/hr_timesheet_invoice/report/report_analytic_view.xml b/addons/hr_timesheet_invoice/report/report_analytic_view.xml
index ed3c86939cd..c196beae880 100644
--- a/addons/hr_timesheet_invoice/report/report_analytic_view.xml
+++ b/addons/hr_timesheet_invoice/report/report_analytic_view.xml
@@ -113,14 +113,14 @@
search
-
+
-
-
-
-
+
+
+
+
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
index 4e1eebb5f4c..8f5c9638a53 100644
--- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
+++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
@@ -167,33 +167,33 @@ class hr_timesheet_sheet(osv.osv):
ids_signout = self.pool.get('hr.attendance').search(cr,uid,[('sheet_id', '=', sheet_id),('action','=','sign_out')])
if len(ids_signin) != len(ids_signout):
- raise osv.except_osv(('Warning !'),_('The timesheet cannot be validated as it does not contain equal no. of sign ins and sign outs!'))
+ raise osv.except_osv(('Warning !'),_('The timesheet cannot be validated as it does not contain an equal number of sign ins and sign outs!'))
return True
def copy(self, cr, uid, ids, *args, **argv):
- raise osv.except_osv(_('Error !'), _('You cannot duplicate a timesheet !'))
+ raise osv.except_osv(_('Error !'), _('You cannot duplicate a timesheet!'))
def create(self, cr, uid, vals, *args, **argv):
if 'employee_id' in vals:
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).user_id:
- raise osv.except_osv(_('Error !'), _('You cannot create a timesheet for an employee that does not have any user defined !'))
+ raise osv.except_osv(_('Error !'), _('In order to create a timesheet for this employee, you must assign it to a user!'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).product_id:
- raise osv.except_osv(_('Error !'), _('You cannot create a timesheet for an employee that does not have any product defined !'))
+ 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:
- raise osv.except_osv(_('Error !'), _('You cannot create a timesheet for an employee that does not have any analytic journal defined !'))
+ raise osv.except_osv(_('Error !'), _('In order to create a timesheet for this employee, you must assign the employee to an analytic journal, like \'Timesheet\'!'))
return super(hr_timesheet_sheet, self).create(cr, uid, vals, *args, **argv)
def write(self, cr, uid, ids, vals, *args, **argv):
if 'employee_id' in vals:
new_user_id = self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).user_id.id or False
if not new_user_id:
- raise osv.except_osv(_('Error !'), _('You cannot create a timesheet for an employee that does not have any user defined !'))
+ raise osv.except_osv(_('Error !'), _('In order to create a timesheet for this employee, you must assign it to a user!'))
if not self._sheet_date(cr, uid, ids, forced_user_id=new_user_id):
- raise osv.except_osv(_('Error !'), _('You can not have 2 timesheets that overlaps !\nPlease use the menu \'My Current Timesheet\' to avoid this problem.'))
+ raise osv.except_osv(_('Error !'), _('You cannot have 2 timesheets that overlaps!\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:
- raise osv.except_osv(_('Error !'), _('You cannot create a timesheet for an employee that does not have any product defined !'))
+ 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:
- raise osv.except_osv(_('Error !'), _('You cannot create a timesheet for an employee that does not have any analytic journal defined !'))
+ raise osv.except_osv(_('Error !'), _('In order to create a timesheet for this employee, you must assign the employee to an analytic journal!'))
return super(hr_timesheet_sheet, self).write(cr, uid, ids, vals, *args, **argv)
def button_confirm(self, cr, uid, ids, context=None):
@@ -355,7 +355,7 @@ class hr_timesheet_sheet(osv.osv):
_constraints = [
- (_sheet_date, 'You can not have 2 timesheets that overlaps !\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']),
+ (_sheet_date, 'You cannot have 2 timesheets that overlaps !\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']),
(_date_current_check, 'You must select a Current date which is in the timesheet dates !', ['date_current']),
]
@@ -377,9 +377,9 @@ class hr_timesheet_sheet(osv.osv):
sheets = self.read(cr, uid, ids, ['state','total_attendance'], context=context)
for sheet in sheets:
if sheet['state'] in ('confirm', 'done'):
- raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sheet(s) which are already confirmed !'))
+ raise osv.except_osv(_('Invalid action !'), _('You cannot delete a timesheet which is already confirmed!'))
elif sheet['total_attendance'] <> 0.00:
- raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sheet(s) which have attendance entries encoded !'))
+ raise osv.except_osv(_('Invalid action !'), _('You cannot delete a timesheet which have attendance entries!'))
return super(hr_timesheet_sheet, self).unlink(cr, uid, ids, context=context)
hr_timesheet_sheet()
@@ -490,7 +490,7 @@ class hr_timesheet_line(osv.osv):
return True
_constraints = [
- (_check_sheet_state, 'You can not modify an entry in a Confirmed/Done timesheet !.', ['state']),
+ (_check_sheet_state, 'You cannot modify an entry in a Confirmed/Done timesheet !.', ['state']),
]
def unlink(self, cr, uid, ids, *args, **kwargs):
@@ -612,7 +612,7 @@ class hr_attendance(osv.osv):
if 'sheet_id' in context:
ts = self.pool.get('hr_timesheet_sheet.sheet').browse(cr, uid, context['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 !'))
+ 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:
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
index 9569149860c..87f212112c5 100644
--- a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
+++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
@@ -33,7 +33,7 @@
search
-
+
@@ -159,11 +159,11 @@
search
-
+
-
+ search
-
+ search
-
+
diff --git a/addons/html_view/i18n/mk.po b/addons/html_view/i18n/mk.po
new file mode 100644
index 00000000000..dd98a0f9818
--- /dev/null
+++ b/addons/html_view/i18n/mk.po
@@ -0,0 +1,63 @@
+# Macedonian translation for openobject-addons
+# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2011-01-11 11:15+0000\n"
+"PO-Revision-Date: 2011-09-14 08:15+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Macedonian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2011-09-15 04:47+0000\n"
+"X-Generator: Launchpad (build 13921)\n"
+
+#. module: html_view
+#: field:html.view,name:0
+msgid "Name"
+msgstr "Име"
+
+#. module: html_view
+#: field:html.view,comp_id:0
+msgid "Company"
+msgstr "Компанија"
+
+#. module: html_view
+#: model:ir.actions.act_window,name:html_view.action_html_view_form
+#: model:ir.ui.menu,name:html_view.html_form
+msgid "Html Test"
+msgstr "Html тест"
+
+#. module: html_view
+#: view:html.view:0
+msgid "Html Example"
+msgstr "Html пример"
+
+#. module: html_view
+#: model:ir.module.module,shortdesc:html_view.module_meta_information
+msgid "Html View"
+msgstr "Html преглед"
+
+#. module: html_view
+#: field:html.view,bank_ids:0
+msgid "Banks"
+msgstr "Банки"
+
+#. module: html_view
+#: model:ir.module.module,description:html_view.module_meta_information
+msgid ""
+"\n"
+" This is the test module which shows html tag supports in normal xml form "
+"view.\n"
+" "
+msgstr ""
+
+#. module: html_view
+#: model:ir.model,name:html_view.model_html_view
+msgid "html.view"
+msgstr "html.view"
diff --git a/addons/idea/idea.py b/addons/idea/idea.py
index 0e6e89f15b5..49d0cbca155 100644
--- a/addons/idea/idea.py
+++ b/addons/idea/idea.py
@@ -64,7 +64,7 @@ class idea_category(osv.osv):
_order = 'parent_id,name asc'
_constraints = [
- (osv.osv._check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
+ (osv.osv._check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id'])
]
idea_category()
diff --git a/addons/idea/idea_view.xml b/addons/idea/idea_view.xml
index 4111cd03962..59111a11d04 100644
--- a/addons/idea/idea_view.xml
+++ b/addons/idea/idea_view.xml
@@ -8,12 +8,12 @@
search
-
+
-
+
@@ -169,12 +169,12 @@
search
-
+
-
+
@@ -288,17 +288,17 @@
search
-
+
-
-
-
+
+
+
-
+
diff --git a/addons/idea/report/report_vote_view.xml b/addons/idea/report/report_vote_view.xml
index 26608a71301..eb5cfe3c28b 100644
--- a/addons/idea/report/report_vote_view.xml
+++ b/addons/idea/report/report_vote_view.xml
@@ -37,14 +37,12 @@
help="Idea Vote created in current month"/>
@@ -64,7 +62,7 @@
-
+
diff --git a/addons/import_base/__openerp__.py b/addons/import_base/__openerp__.py
index 962541f427a..f244338a7e3 100644
--- a/addons/import_base/__openerp__.py
+++ b/addons/import_base/__openerp__.py
@@ -31,7 +31,7 @@
'website': 'http://www.openerp.com',
'depends': ['base'],
'init_xml': [],
- 'update_xml': [],
+ 'update_xml': ["import_base_view.xml"],
'demo_xml': [],
'test': [], #TODO provide test
'installable': True,
diff --git a/addons/import_base/import_base_view.xml b/addons/import_base/import_base_view.xml
new file mode 100644
index 00000000000..1368b50b6e4
--- /dev/null
+++ b/addons/import_base/import_base_view.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/import_base/import_framework.py b/addons/import_base/import_framework.py
index 9b25ecb1ecf..b558b24696f 100644
--- a/addons/import_base/import_framework.py
+++ b/addons/import_base/import_framework.py
@@ -22,11 +22,13 @@ import pprint
import mapper
import pooler
import tools
-
+from tools.translate import _
from threading import Thread
import datetime
import logging
+import StringIO
+import traceback
pp = pprint.PrettyPrinter(indent=4)
@@ -46,8 +48,10 @@ class import_framework(Thread):
"""
DO_NOT_FIND_DOMAIN = [('id', '=', 0)]
+ #TODO don't use context to pass credential parameters
def __init__(self, obj, cr, uid, instance_name, module_name, email_to_notify=False, context=None):
Thread.__init__(self)
+ self.external_id_field = 'id'
self.obj = obj
self.cr = cr
self.uid = uid
@@ -56,10 +60,9 @@ class import_framework(Thread):
self.context = context or {}
self.email = email_to_notify
self.table_list = []
+ self.logger = logging.getLogger('import_framework')
self.initialize()
-
-
-
+
"""
Abstract Method to be implemented in
the real instance
@@ -71,6 +74,13 @@ class import_framework(Thread):
"""
pass
+ def init_run(self):
+ """
+ call after intialize run in the thread, not in the main process
+ TO use for long initialization operation
+ """
+ pass
+
def get_data(self, table):
"""
@return: a list of dictionaries
@@ -78,6 +88,22 @@ class import_framework(Thread):
"""
return [{}]
+ def get_link(self, from_table, ids, to_table):
+ """
+ @return: a dictionaries that contains the association between the id (from_table)
+ and the list (to table) of id linked
+ """
+ return {}
+
+ def get_external_id(self, data):
+ """
+ @return the external id
+ the default implementation return self.external_id_field (that has 'id') by default
+ if the name of id field is different, you can overwrite this method or change the value
+ of self.external_id_field
+ """
+ return data[self.external_id_field]
+
def get_mapping(self):
"""
@return: { TABLE_NAME : {
@@ -140,7 +166,7 @@ class import_framework(Thread):
and each data_i have a external id => in data_id['id']
"""
if not datas:
- return (0, 'No data in this table')
+ return (0, 'No data found')
mapping['id'] = 'id_new'
res = []
@@ -154,14 +180,15 @@ class import_framework(Thread):
for k, field_name in self_dependencies:
data[k] = data.get(field_name) and self._generate_xml_id(data.get(field_name), table)
- data['id_new'] = self._generate_xml_id(data['id'], table)
+ data['id_new'] = self._generate_xml_id(self.get_external_id(data), table)
fields, values = self._fields_mapp(data, mapping, table)
res.append(values)
model_obj = self.obj.pool.get(model)
if not model_obj:
- raise ValueError("%s is not a valid model name" % model)
-
+ raise ValueError(_("%s is not a valid model name") % model)
+ self.logger.debug(_("fields imported : "))
+ self.logger.debug(fields)
(p, r, warning, s) = model_obj.import_data(self.cr, self.uid, fields, res, mode='update', current_module=self.module_name, noupdate=True, context=self.context)
for (field, field_name) in self_dependencies:
self._import_self_dependencies(model_obj, field, datas)
@@ -296,10 +323,14 @@ class import_framework(Thread):
"""
domain_search = not domain_search and [('name', 'ilike', name)] or domain_search
obj = self.obj.pool.get(model)
+ if not obj: #if the model doesn't exist
+ return False
+
xml_id = self._generate_xml_id(name, table)
xml_ref = self.mapped_id_if_exist(model, domain_search, table, name)
fields.append('id')
data.append(xml_id)
+
obj.import_data(self.cr, self.uid, fields, [data], mode='update', current_module=self.module_name, noupdate=True, context=self.context)
return xml_ref or xml_id
@@ -343,8 +374,10 @@ class import_framework(Thread):
"""
self.data_started = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.cr = pooler.get_db(self.cr.dbname).cursor()
+ error = False
+ result = []
try:
- result = []
+ self.init_run()
imported = set() #to invoid importing 2 times the sames modules
for table in self.table_list:
to_import = self.get_mapping()[table].get('import', True)
@@ -352,16 +385,22 @@ class import_framework(Thread):
res = self._resolve_dependencies(self.get_mapping()[table].get('dependencies', []), imported)
result.extend(res)
if to_import:
+ self.logger.debug(_("import : ") + table )
(position, warning) = self._import_table(table)
result.append((table, position, warning))
imported.add(table)
self.cr.commit()
-
+
+ except Exception, err:
+ sh = StringIO.StringIO()
+ traceback.print_exc(file=sh)
+ error = sh.getvalue()
+ print error
finally:
self.cr.close()
+
self.date_ended = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- self._send_notification_email(result)
-
+ self._send_notification_email(result, error)
def _resolve_dependencies(self, dep, imported):
"""
@@ -375,25 +414,29 @@ class import_framework(Thread):
res = self._resolve_dependencies(self.get_mapping()[dependency].get('dependencies', []), imported)
result.extend(res)
if to_import:
+ self.logger.debug("import dependency : " + dependency)
r = self._import_table(dependency)
(position, warning) = r
result.append((dependency, position, warning))
imported.add(dependency)
return result
- def _send_notification_email(self, result):
- if not self.email:
- return
+ def _send_notification_email(self, result, error):
+ if not self.email or not self.email[0]:
+ return False
+
tools.email_send(
- 'import_sugarcrm@module.openerp',
+ 'import@module.openerp',
self.email,
- self.get_email_subject(result),
- self.get_email_body(result),
+ self.get_email_subject(result, error),
+ self.get_email_body(result, error),
)
- logger = logging.getLogger('import_sugarcam')
- logger.info("Import finished, notification email sended")
+ if error:
+ self.logger.error(_("Import failed due to an unexpected error"))
+ else:
+ self.logger.info(_("Import finished, notification email sended"))
- def get_email_subject(self, result):
+ def get_email_subject(self, result, error=False):
"""
This method define the subject of the email send by openerp at the end of import
@param result: a list of tuple
@@ -401,9 +444,11 @@ class import_framework(Thread):
@return the subject of the mail
"""
- return "Import of your data finished at %s" % self.date_ended
+ if error:
+ return _("Data Import failed at %s due to an unexpected error") % self.date_ended
+ return _("Import of your data finished at %s") % self.date_ended
- def get_email_body(self, result):
+ def get_email_body(self, result, error=False):
"""
This method define the body of the email send by openerp at the end of import. The body is separated in two part
the header (@see get_body_header), and the generate body with the list of table and number of record imported.
@@ -414,19 +459,43 @@ class import_framework(Thread):
"""
- body = "started at %s and finished at %s \n" % (self.data_started, self.date_ended)
+ body = _("started at %s and finished at %s \n") % (self.data_started, self.date_ended)
+ if error:
+ body += _("but failed, in consequence no data were imported to keep database consistency \n error : \n") + error
+
for (table, nb, warning) in result:
if not warning:
- warning = "with no warning"
+ warning = _("with no warning")
else:
- warning = "with warning : %s" % warning
- body += "%s records were imported from table %s, %s \n" % (nb, table, warning)
+ warning = _("with warning : %s") % warning
+ body += _("%s has been successfully imported from %s %s, %s \n") % (nb, self.instance_name, table, warning)
return self.get_body_header(result) + "\n\n" + body
def get_body_header(self, result):
"""
@return the first sentences written in the mail's body
"""
- return "The import of data \n instance name : %s \n" % self.instance_name
+ return _("The import of data \n instance name : %s \n") % self.instance_name
-#For example of use see import_sugarcrm
+
+ #TODO documentation test
+ def run_test(self):
+ back_get_data = self.get_data
+ back_get_link = self.get_link
+ back_init = self.initialize
+ self.get_data = self.get_data_test
+ self.get_link = self.get_link_test
+ self.initialize = self.intialize_test
+ self.run()
+ self.get_data = back_get_data
+ self.get_link = back_get_link
+ self.initialize = back_init
+
+ def get_data_test(self, table):
+ return [{}]
+
+ def get_link_test(self, from_table, ids, to_table):
+ return {}
+
+ def intialize_test(self):
+ pass
diff --git a/addons/import_base/mapper.py b/addons/import_base/mapper.py
index 7f97ed0b615..0827509ffab 100644
--- a/addons/import_base/mapper.py
+++ b/addons/import_base/mapper.py
@@ -18,7 +18,7 @@
# along with this program. If not, see .
#
##############################################################################
-
+import tools
class mapper(object):
"""
@@ -50,14 +50,13 @@ class concat(mapper):
Use : contact('field_name1', 'field_name2', delimiter='_')
concat value of fields using the delimiter, delimiter is optional
and by default is a space
-
"""
def __init__(self, *arg, **delimiter):
self.arg = arg
self.delimiter = delimiter and delimiter.get('delimiter', ' ') or ' '
def __call__(self, external_values):
- return self.delimiter.join(map(lambda x : external_values.get(x,''), self.arg))
+ return self.delimiter.join(map(lambda x : tools.ustr(external_values.get(x,'')), self.arg))
class ppconcat(mapper):
"""
@@ -71,7 +70,7 @@ class ppconcat(mapper):
self.delimiter = delimiter and delimiter.get('delimiter', ' ') or '\n\n'
def __call__(self, external_values):
- return self.delimiter.join(map(lambda x : x + ": " + external_values.get(x,''), self.arg))
+ return self.delimiter.join(map(lambda x : x + ": " + tools.ustr(external_values.get(x,'')), self.arg))
class const(mapper):
"""
@@ -105,7 +104,6 @@ class value(mapper):
val = external_values.get(self.fallback, self.default)
return val
-
class map_val(mapper):
"""
@@ -169,5 +167,3 @@ class call(mapper):
else:
args.append(arg)
return self.fun(external_values, *args)
-
-
\ No newline at end of file
diff --git a/addons/mail_gateway/scripts/__init__.py b/addons/import_sugarcrm/__init__.py
similarity index 93%
rename from addons/mail_gateway/scripts/__init__.py
rename to addons/import_sugarcrm/__init__.py
index 9d7f6bf56c9..250589105b0 100644
--- a/addons/mail_gateway/scripts/__init__.py
+++ b/addons/import_sugarcrm/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
-#
+#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL ().
#
@@ -15,10 +15,11 @@
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
+# along with this program. If not, see .
#
##############################################################################
-import openerp_mailgate
-
+import import_sugarcrm
+import sugar
+import wizard
diff --git a/addons/mail_gateway/__openerp__.py b/addons/import_sugarcrm/__openerp__.py
similarity index 66%
rename from addons/mail_gateway/__openerp__.py
rename to addons/import_sugarcrm/__openerp__.py
index af1e28e0c2c..071f58099b3 100644
--- a/addons/mail_gateway/__openerp__.py
+++ b/addons/import_sugarcrm/__openerp__.py
@@ -20,30 +20,20 @@
##############################################################################
{
- 'name': 'Email Gateway System',
+ 'name': 'Import SugarCRM Data into OpenERP Module.',
'version': '1.0',
- 'category': 'Tools',
- 'complexity': "easy",
- 'description': """
-The generic email gateway system allows to send and receive emails.
-===================================================================
-
- * History of emails
- * Easy integration with any module""",
+ 'category': 'Generic Modules',
+ 'description': """This Module Import SugarCRM "Leads", "Opportunities", "Users", "Accounts",
+ "Contacts", "Employees", Meetings, Phonecalls, Emails, and Project, Project Tasks Data into OpenERP Module.""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
- 'depends': ['base'],
+ 'depends': ['import_base','crm', 'document'],
'init_xml': [],
- 'update_xml': [
- "mail_gateway_view.xml",
- "res_partner_view.xml",
- 'security/ir.model.access.csv'
-
- ],
+ 'update_xml': ["wizard/import_message_view.xml",
+ "import_sugarcrm_view.xml"],
'demo_xml': [],
+ 'test': [],
'installable': True,
'active': False,
- 'certificate': '001056784984222247309',
- 'images': ['images/customer_history.jpeg','images/messages_form.jpeg','images/messages_list.jpeg'],
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/import_sugarcrm/doc/import_sugarcrm_installation_manual.odt b/addons/import_sugarcrm/doc/import_sugarcrm_installation_manual.odt
new file mode 100644
index 00000000000..2dfde16bc97
Binary files /dev/null and b/addons/import_sugarcrm/doc/import_sugarcrm_installation_manual.odt differ
diff --git a/addons/import_sugarcrm/doc/import_sugarcrm_user_manual.odt b/addons/import_sugarcrm/doc/import_sugarcrm_user_manual.odt
new file mode 100644
index 00000000000..2a16eeb566c
Binary files /dev/null and b/addons/import_sugarcrm/doc/import_sugarcrm_user_manual.odt differ
diff --git a/addons/import_sugarcrm/doc/import_sugarcrm_zsi_installation.txt b/addons/import_sugarcrm/doc/import_sugarcrm_zsi_installation.txt
new file mode 100644
index 00000000000..e399d63120b
--- /dev/null
+++ b/addons/import_sugarcrm/doc/import_sugarcrm_zsi_installation.txt
@@ -0,0 +1,8 @@
+Install the SOAP framwwork for Python :
+ 1. download the soap framework from http://pypi.python.org/pypi/ZSI/
+ extract the file “ZSI-2.0-rc3.tar.gz” and run following 2 commands
+ ./setup.py build
+ ./setup.py install
+ this will install soap framework for python.
+
+or use the ZSI package of your distribution
diff --git a/addons/import_sugarcrm/import_sugarcrm.py b/addons/import_sugarcrm/import_sugarcrm.py
new file mode 100644
index 00000000000..b27748d48be
--- /dev/null
+++ b/addons/import_sugarcrm/import_sugarcrm.py
@@ -0,0 +1,1147 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+from osv import fields, osv
+import sugar
+from tools.translate import _
+from import_base.import_framework import *
+from import_base.mapper import *
+from datetime import datetime
+import base64
+import pprint
+pp = pprint.PrettyPrinter(indent=4)
+#copy old import here
+
+import htmllib
+
+def unescape_htmlentities(s):
+ p = htmllib.HTMLParser(None)
+ p.save_bgn()
+ p.feed(s)
+ return p.save_end()
+
+class related_ref(dbmapper):
+ def __init__(self, type):
+ self.type = type
+
+ def __call__(self, external_val):
+ if external_val.get('parent_type') in self.type and external_val.get('parent_id'):
+ return self.parent.xml_id_exist(external_val['parent_type'], external_val['parent_id'])
+ return ''
+
+class sugar_import(import_framework):
+ URL = False
+ TABLE_CONTACT = 'Contacts'
+ TABLE_ACCOUNT = 'Accounts'
+ TABLE_USER = 'Users'
+ TABLE_EMPLOYEE = 'Employees'
+ TABLE_RESSOURCE = "resource"
+ TABLE_OPPORTUNITY = 'Opportunities'
+ TABLE_LEAD = 'Leads'
+ TABLE_STAGE = 'crm_stage'
+ TABLE_ATTENDEE = 'calendar_attendee'
+ TABLE_CALL = 'Calls'
+ TABLE_MEETING = 'Meetings'
+ TABLE_TASK = 'Tasks'
+ TABLE_PROJECT = 'Project'
+ TABLE_PROJECT_TASK = 'ProjectTask'
+ TABLE_BUG = 'Bugs'
+ TABLE_CASE = 'Cases'
+ TABLE_NOTE = 'Notes'
+ TABLE_EMAIL = 'Emails'
+ TABLE_COMPAIGN = 'Campaigns'
+ TABLE_DOCUMENT = 'Documents'
+ TABLE_HISTORY_ATTACHMNET = 'history_attachment'
+
+ MAX_RESULT_PER_PAGE = 200
+
+ def initialize(self):
+ #login
+ PortType,sessionid = sugar.login(self.context.get('username',''), self.context.get('password',''), self.context.get('url',''))
+ if sessionid == '-1':
+ raise osv.except_osv(_('Error !'), _('Authentication error !\nBad Username or Password or bad SugarSoap Api url !'))
+ self.context['port'] = PortType
+ self.context['session_id'] = sessionid
+
+ def get_data(self, table):
+ offset = 0
+ res = []
+ while True:
+ r = sugar.search(self.context.get('port'), self.context.get('session_id'), table, offset, self.MAX_RESULT_PER_PAGE)
+ res.extend(r)
+ if len(r) < self.MAX_RESULT_PER_PAGE:
+ break;
+ offset += self.MAX_RESULT_PER_PAGE
+ return res
+
+ #def get_link(self, from_table, ids, to_table):
+ #return sugar.relation_search(self.context.get('port'), self.context.get('session_id'), from_table, module_id=ids, related_module=to_table)
+
+ """
+ Common import method
+ """
+ def get_category(self, val, model, name):
+ fields = ['name', 'object_id']
+ data = [name, model]
+ return self.import_object(fields, data, 'crm.case.categ', 'crm_categ', name, [('object_id.model','=',model), ('name', 'ilike', name)])
+
+ def get_job_title(self, dict, salutation):
+ fields = ['shortcut', 'name', 'domain']
+ if salutation:
+ data = [salutation, salutation, 'Contact']
+ return self.import_object(fields, data, 'res.partner.title', 'contact_title', salutation, [('shortcut', '=', salutation)])
+
+ def get_channel_id(self, dict, val):
+ if not val:
+ return False
+ fields = ['name']
+ data = [val]
+ return self.import_object(fields, data, 'res.partner.canal', 'crm_channel', val)
+
+ def get_all_states(self, external_val, country_id):
+ """Get states or create new state unless country_id is False"""
+ state_code = external_val[0:3] #take the tree first char
+ fields = ['country_id/id', 'name', 'code']
+ data = [country_id, external_val, state_code]
+ if country_id:
+ return self.import_object(fields, data, 'res.country.state', 'country_state', external_val)
+ return False
+
+ def get_all_countries(self, val):
+ """Get Country, if no country match do not create anything, to avoid duplicate country code"""
+ return self.mapped_id_if_exist('res.country', [('name', 'ilike', val)], 'country', val)
+
+ def get_float_time(self, dict, hour, min):
+ min = int(min) * 100 / 60
+ return "%s.%i" % (hour, min)
+
+ """
+ import Documents
+ """
+
+ def import_related_document(self, val):
+ res_model = False
+ res_id = False
+ sugar_document_account = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Documents', module_id=val.get('id'), related_module='Accounts', query=None, deleted=None)
+ sugar_document_contact = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Documents', module_id=val.get('id'), related_module=self.TABLE_CONTACT, query=None, deleted=None)
+ sugar_document_opportunity = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Documents', module_id=val.get('id'), related_module=self.TABLE_OPPORTUNITY, query=None, deleted=None)
+ sugar_document_case = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Documents', module_id=val.get('id'), related_module=self.TABLE_CASE, query=None, deleted=None)
+ sugar_document_bug = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Documents', module_id=val.get('id'), related_module=self.TABLE_BUG, query=None, deleted=None)
+ if sugar_document_account:
+ res_id = self.get_mapped_id(self.TABLE_ACCOUNT,sugar_document_account[0])
+ res_model = 'res.partner'
+ elif sugar_document_contact:
+ res_id = self.get_mapped_id(self.TABLE_CONTACT, sugar_document_contact[0])
+ res_model = 'res.partner.address'
+ elif sugar_document_opportunity:
+ res_id = self.get_mapped_id(self.TABLE_OPPORTUNITY, sugar_document_opportunity[0])
+ res_model = 'crm.lead'
+ elif sugar_document_case:
+ res_id = self.get_mapped_id(self.TABLE_CASE, sugar_document_case[0])
+ res_model = 'crm.claim'
+ elif sugar_document_bug:
+ res_id = self.get_mapped_id(self.TABLE_BUG, sugar_document_bug[0])
+ res_model = 'project.issue'
+ return res_id,res_model
+
+ def import_document(self, val):
+ File,Filename = sugar.get_document_revision_search(self.context.get('port'), self.context.get('session_id'), val.get('document_revision_id'))
+ #File = base64.encodestring(File)
+ res_id, res_model = self.import_related_document(val)
+ val['res_id'] = res_id
+ val['res_model'] = res_model
+ if File:
+ val['datas'] = File
+ val['datas_fname'] = Filename
+ return val
+
+ def get_document_mapping(self):
+ return {
+ 'model' : 'ir.attachment',
+ 'dependencies' : [self.TABLE_USER],
+ 'hook' : self.import_document,
+ 'map' : {
+ 'name':'document_name',
+ 'description': ppconcat('description'),
+ 'datas': 'datas',
+ 'datas_fname': 'datas_fname',
+ 'res_model': 'res_model',
+ 'res_id': 'res_id',
+ }
+ }
+
+
+ """
+ import Emails
+ """
+
+
+ def import_email(self, val):
+ vals = sugar.email_search(self.context.get('port'), self.context.get('session_id'), self.TABLE_EMAIL, val.get('id'))
+ model_obj = self.obj.pool.get('ir.model.data')
+ for val in vals:
+ xml_id = self.xml_id_exist(val.get('parent_type'), val.get('parent_id'))
+ model_ids = model_obj.search(self.cr, self.uid, [('name', 'like', xml_id)])
+ if model_ids:
+ model = model_obj.browse(self.cr, self.uid, model_ids)[0]
+ if model.model == 'res.partner':
+ val['partner_id/.id'] = model.res_id
+ else:
+ val['res_id'] = model.res_id
+ val['model'] = model.model
+ return val
+
+ def get_email_mapping(self):
+ return {
+ 'model' : 'mailgate.message',
+ 'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT, self.TABLE_LEAD, self.TABLE_OPPORTUNITY, self.TABLE_MEETING, self.TABLE_CALL],
+ 'hook' : self.import_email,
+ 'map' : {
+ 'name':'name',
+ 'history' : const("1"),
+ 'date':'date_sent',
+ 'email_from': 'from_addr_name',
+ 'email_to': 'to_addrs_names',
+ 'email_cc': 'cc_addrs_names',
+ 'email_bcc': 'bcc_addrs_names',
+ 'message_id': 'message_id',
+ 'res_id': 'res_id',
+ 'model': 'model',
+ 'partner_id/.id': 'partner_id/.id',
+ 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
+ 'description': ppconcat('description', 'description_html'),
+ }
+ }
+
+ """
+ import History(Notes)
+ """
+
+
+ def import_history(self, val):
+ model_obj = self.obj.pool.get('ir.model.data')
+ xml_id = self.xml_id_exist(val.get('parent_type'), val.get('parent_id'))
+ model_ids = model_obj.search(self.cr, self.uid, [('name', 'like', xml_id)])
+ if model_ids:
+ model = model_obj.browse(self.cr, self.uid, model_ids)[0]
+ if model.model == 'res.partner':
+ val['partner_id/.id'] = model.res_id
+ val['res_id'] = model.res_id
+ val['model'] = model.model
+ File, Filename = sugar.attachment_search(self.context.get('port'), self.context.get('session_id'), self.TABLE_NOTE, val.get('id'))
+ if File:
+ val['datas'] = File
+ val['datas_fname'] = Filename
+ return val
+
+ def get_history_mapping(self):
+ return {
+ 'model' : 'ir.attachment',
+ 'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT, self.TABLE_LEAD, self.TABLE_OPPORTUNITY, self.TABLE_MEETING, self.TABLE_CALL, self.TABLE_EMAIL],
+ 'hook' : self.import_history,
+ 'map' : {
+ 'name':'name',
+ 'user_id/id': ref(self.TABLE_USER, 'created_by'),
+ 'description': ppconcat('description', 'description_html'),
+ 'res_id': 'res_id',
+ 'res_model': 'model',
+ 'partner_id/.id' : 'partner_id/.id',
+ 'datas' : 'datas',
+ 'datas_fname' : 'datas_fname'
+ }
+ }
+
+ """
+ import Claims(Cases)
+ """
+ def get_claim_priority(self, val):
+ priority_dict = {
+ 'P1': '2',
+ 'P2': '3',
+ 'P3': '4'
+ }
+ return priority_dict.get(val.get('priority'), '')
+
+ def get_contact_info_from_account(self, val):
+ partner_id = self.get_mapped_id(self.TABLE_ACCOUNT, val.get('account_id'))
+ partner_address_id = False
+ partner_phone = False
+ partner_email = False
+ partner = self.obj.pool.get('res.partner').browse(self.cr, self.uid, [partner_id])[0]
+ if partner.address and partner.address[0]:
+ address = partner.address[0]
+ partner_address_id = address.id
+ partner_phone = address.phone
+ partner_email = address.email
+ return partner_address_id, partner_phone,partner_email
+
+ def import_crm_claim(self, val):
+ partner_address_id, partner_phone,partner_email = self.get_contact_info_from_account(val)
+ val['partner_address_id/.id'] = partner_address_id
+ val['partner_phone'] = partner_phone
+ val['email_from'] = partner_email
+ return val
+
+ def get_crm_claim_mapping(self):
+ return {
+ 'model' : 'crm.claim',
+ 'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT, self.TABLE_LEAD],
+ 'hook' : self.import_crm_claim,
+ 'map' : {
+ 'name': concat('case_number','name', delimiter='-'),
+ 'date': 'date_entered',
+ 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
+ 'description': ppconcat('description', 'resolution', 'work_log'),
+ 'partner_id/id': ref(self.TABLE_ACCOUNT, 'account_id'),
+ 'partner_address_id/.id': 'partner_address_id/.id',
+ 'categ_id/id': call(self.get_category, 'crm.claim', value('type')),
+ 'partner_phone': 'partner_phone',
+ 'email_from': 'email_from',
+ 'priority': self.get_claim_priority,
+ 'state': map_val('status', self.project_issue_state)
+ }
+ }
+ """
+ Import Project Issue(Bugs)
+ """
+ project_issue_state = {
+ 'New' : 'draft',
+ 'Assigned':'open',
+ 'Closed': 'done',
+ 'Pending': 'pending',
+ 'Rejected': 'cancel',
+ }
+
+ def get_project_issue_priority(self, val):
+ priority_dict = {
+ 'Urgent': '1',
+ 'High': '2',
+ 'Medium': '3',
+ 'Low': '4'
+ }
+ return priority_dict.get(val.get('priority'), '')
+
+ def get_bug_project_id(self, dict, val):
+ fields = ['name']
+ data = [val]
+ return self.import_object(fields, data, 'project.project', 'project_issue', val)
+
+ def get_project_issue_mapping(self):
+ return {
+ 'model' : 'project.issue',
+ 'dependencies' : [self.TABLE_USER],
+ 'map' : {
+ 'name': concat('bug_number', 'name', delimiter='-'),
+ 'project_id/id': call(self.get_bug_project_id, 'sugarcrm_bugs'),
+ 'categ_id/id': call(self.get_category, 'project.issue', value('type')),
+ 'description': ppconcat('description', 'source', 'resolution', 'work_log', 'found_in_release', 'release_name', 'fixed_in_release_name', 'fixed_in_release'),
+ 'priority': self.get_project_issue_priority,
+ 'state': map_val('status', self.project_issue_state),
+ 'assigned_to/id' : ref(self.TABLE_USER, 'assigned_user_id'),
+ }
+ }
+
+ """
+ import Project Tasks
+ """
+ project_task_state = {
+ 'Not Started': 'draft',
+ 'In Progress': 'open',
+ 'Completed': 'done',
+ 'Pending Input': 'pending',
+ 'Deferred': 'cancelled',
+ }
+
+ def get_project_task_priority(self, val):
+ priority_dict = {
+ 'High': '0',
+ 'Medium': '2',
+ 'Low': '3'
+ }
+ return priority_dict.get(val.get('priority'), '')
+
+ def get_project_task_mapping(self):
+ return {
+ 'model' : 'project.task',
+ 'dependencies' : [self.TABLE_USER, self.TABLE_PROJECT],
+ 'map' : {
+ 'name': 'name',
+ 'date_start': 'date_start',
+ 'date_end': 'date_finish',
+ 'project_id/id': ref(self.TABLE_PROJECT, 'project_id'),
+ 'planned_hours': 'estimated_effort',
+ 'priority': self.get_project_task_priority,
+ 'description': ppconcat('description','milestone_flag', 'project_task_id', 'task_number', 'percent_complete'),
+ 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
+ 'partner_id/id': 'partner_id/id',
+ 'contact_id/id': 'contact_id/id',
+ 'state': map_val('status', self.project_task_state)
+ }
+ }
+
+ """
+ import Projects
+ """
+ project_state = {
+ 'Draft' : 'draft',
+ 'In Review': 'open',
+ 'Published': 'close'
+ }
+
+ def import_project_account(self, val):
+ partner_id = False
+ partner_invoice_id = False
+ sugar_project_account = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Project', module_id=val.get('id'), related_module=self.TABLE_ACCOUNT, query=None, deleted=None)
+ sugar_project_contact = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Project', module_id=val.get('id'), related_module=self.TABLE_CONTACT, query=None, deleted=None)
+ for contact_id in sugar_project_contact:
+ partner_invoice_id = self.get_mapped_id(self.TABLE_CONTACT, contact_id)
+ for account_id in sugar_project_account:
+ partner_id = self.get_mapped_id(self.TABLE_ACCOUNT, account_id)
+ return partner_id, partner_invoice_id
+
+ def import_project(self, val):
+ partner_id, partner_invoice_id = self.import_project_account(val)
+ val['partner_id/.id'] = partner_id
+ val['contact_id/.id'] = partner_invoice_id
+ return val
+
+ def get_project_mapping(self):
+ return {
+ 'model' : 'project.project',
+ 'dependencies' : [self.TABLE_CONTACT, self.TABLE_ACCOUNT, self.TABLE_USER],
+ 'hook' : self.import_project,
+ 'map' : {
+ 'name': 'name',
+ 'date_start': 'estimated_start_date',
+ 'date': 'estimated_end_date',
+ 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
+ 'partner_id/.id': 'partner_id/.id',
+ 'contact_id/.id': 'contact_id/.id',
+ 'state': map_val('status', self.project_state)
+ }
+ }
+
+ """
+ import Tasks
+ """
+ task_state = {
+ 'Completed' : 'done',
+ 'Not Started':'draft',
+ 'In Progress': 'open',
+ 'Pending Input': 'draft',
+ 'deferred': 'cancel'
+ }
+
+ def import_task(self, val):
+ date = val.get('date_start') or datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ val['date'] = ''.join(date)
+ date_deadline = val.get('date_due') or datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ val['date_deadline'] = ''.join(date_deadline)
+ return val
+
+ def get_task_mapping(self):
+ return {
+ 'model' : 'crm.meeting',
+ 'dependencies' : [self.TABLE_CONTACT, self.TABLE_ACCOUNT, self.TABLE_USER],
+ 'hook' : self.import_task,
+ 'map' : {
+ 'name': 'name',
+ 'date': 'date',
+ 'date_deadline': 'date_deadline',
+ 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
+ 'categ_id/id': call(self.get_category, 'crm.meeting', const('Tasks')),
+ 'partner_id/id': related_ref(self.TABLE_ACCOUNT),
+ 'partner_address_id/id': ref(self.TABLE_CONTACT,'contact_id'),
+ 'state': map_val('status', self.task_state)
+ }
+ }
+
+ """
+ import Calls
+ """
+ #TODO adapt with project trunk-crm-imp
+ call_state = {
+ 'Planned' : 'open',
+ 'Held':'done',
+ 'Not Held': 'pending',
+ }
+
+ def get_calls_mapping(self):
+ return {
+ 'model' : 'crm.phonecall',
+ 'dependencies' : [self.TABLE_ACCOUNT, self.TABLE_CONTACT, self.TABLE_OPPORTUNITY, self.TABLE_LEAD],
+ 'map' : {
+ 'name': 'name',
+ 'date': 'date_start',
+ 'duration': call(self.get_float_time, value('duration_hours'), value('duration_minutes')),
+ 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
+ 'partner_id/id': related_ref(self.TABLE_ACCOUNT),
+ 'partner_address_id/id': related_ref(self.TABLE_CONTACT),
+ 'categ_id/id': call(self.get_category, 'crm.phonecall', value('direction')),
+ 'opportunity_id/id': related_ref(self.TABLE_OPPORTUNITY),
+ 'description': ppconcat('description'),
+ 'state': map_val('status', self.call_state)
+ }
+ }
+
+ """
+ import meeting
+ """
+ meeting_state = {
+ 'Planned' : 'draft',
+ 'Held': 'open',
+ 'Not Held': 'draft',
+ }
+#TODO
+ def get_attendee_id(self, cr, uid, module_name, module_id):
+ contact_id = False
+ user_id = False
+ attendee_id= []
+ attendee_dict = sugar.user_get_attendee_list(self.context.get('port'), self.context.get('session_id'), module_name, module_id)
+ for attendee in attendee_dict:
+ user_id = self.xml_id_exist(self.TABLE_USER, attendee.get('id', False))
+ contact_id = False
+ if not user_id:
+ contact_id = self.xml_id_exist(self.TABLE_CONTACT, attendee.get('id', False))
+ fields = ['user_id/id', 'email', 'partner_address_id/id']
+ data = [user_id, attendee.get('email1'), contact_id]
+ attendee_xml_id = self.import_object(fields, data, 'calendar.attendee', self.TABLE_ATTENDEE, user_id or contact_id or attendee.get('email1'), ['|',('user_id', '=', attendee.get('id')),('partner_address_id','=',attendee.get('id')),('email', '=', attendee.get('email1'))])
+ attendee_id.append(attendee_xml_id)
+ return ','.join(attendee_id)
+
+ def get_alarm_id(self, dict_val, val):
+ alarm_dict = {
+ '60': '1 minute before',
+ '300': '5 minutes before',
+ '600': '10 minutes before',
+ '900': '15 minutes before',
+ '1800':'30 minutes before',
+ '3600': '1 hour before',
+ }
+ return self.mapped_id_if_exist('res.alarm', [('name', 'like', alarm_dict.get(val))], 'alarm', val)
+
+ #TODO attendees
+
+ def import_meeting(self, val):
+ attendee_id = self.get_attendee_id(self.cr, self.uid, 'Meetings', val.get('id')) #TODO
+ val['attendee_ids/id'] = attendee_id
+ return val
+
+ def get_meeting_mapping(self):
+ return {
+ 'model' : 'crm.meeting',
+ 'dependencies' : [self.TABLE_CONTACT, self.TABLE_OPPORTUNITY, self.TABLE_LEAD, self.TABLE_TASK],
+ 'hook': self.import_meeting,
+ 'map' : {
+ 'name': 'name',
+ 'date': 'date_start',
+ 'duration': call(self.get_float_time, value('duration_hours'), value('duration_minutes')),
+ 'location': 'location',
+ 'attendee_ids/id':'attendee_ids/id',
+ 'alarm_id/id': call(self.get_alarm_id, value('reminder_time')),
+ 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
+ 'partner_id/id': related_ref(self.TABLE_ACCOUNT),
+ 'partner_address_id/id': related_ref(self.TABLE_CONTACT),
+ 'state': map_val('status', self.meeting_state)
+ }
+ }
+
+ """
+ import Opportunity
+ """
+ opp_state = {
+ 'Need Analysis' : 'New',
+ 'Closed Lost': 'Lost',
+ 'Closed Won': 'Won',
+ 'Value Proposition': 'Proposition',
+ 'Negotiation/Review': 'Negotiation'
+ }
+
+ def get_opportunity_status(self, sugar_val):
+ fields = ['name', 'type']
+ name = 'Opportunity_' + sugar_val['sales_stage']
+ data = [sugar_val['sales_stage'], 'Opportunity']
+ return self.import_object(fields, data, 'crm.case.stage', self.TABLE_STAGE, name, [('type', '=', 'opportunity'), ('name', 'ilike', sugar_val['sales_stage'])])
+
+ def import_opportunity_contact(self, val):
+ sugar_opportunities_contact = set(sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Opportunities', module_id=val.get('id'), related_module='Contacts', query=None, deleted=None))
+
+ partner_contact_id = False
+ partner_contact_email = False
+ partner_address_obj = self.obj.pool.get('res.partner.address')
+ partner_xml_id = self.name_exist(self.TABLE_ACCOUNT, val['account_name'], 'res.partner')
+
+ for contact in sugar_opportunities_contact:
+ address_id = self.get_mapped_id(self.TABLE_CONTACT, contact)
+ if address_id:
+ address = partner_address_obj.browse(self.cr, self.uid, address_id)
+ partner_name = address.partner_id and address.partner_id.name or False
+ if not partner_name: #link with partner id
+ fields = ['partner_id/id']
+ data = [partner_xml_id]
+ self.import_object(fields, data, 'res.partner.address', self.TABLE_CONTACT, contact, self.DO_NOT_FIND_DOMAIN)
+ if not partner_name or partner_name == val.get('account_name'):
+ partner_contact_id = self.xml_id_exist(self.TABLE_CONTACT, contact)
+ partner_contact_email = address.email
+ return partner_contact_id, partner_contact_email
+
+ def import_opp(self, val):
+ partner_contact_id, partner_contact_email = self.import_opportunity_contact(val)
+ val['partner_address_id/id'] = partner_contact_id
+ val['email_from'] = partner_contact_email
+ return val
+
+ def get_opp_mapping(self):
+ return {
+ 'model' : 'crm.lead',
+ 'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT,self.TABLE_COMPAIGN],
+ 'hook' : self.import_opp,
+ 'map' : {
+ 'name': 'name',
+ 'probability': 'probability',
+ 'partner_id/id': refbyname(self.TABLE_ACCOUNT, 'account_name', 'res.partner'),
+ 'title_action': 'next_step',
+ 'partner_address_id/id': 'partner_address_id/id',
+ 'planned_revenue': 'amount',
+ 'date_deadline': 'date_closed',
+ 'user_id/id' : ref(self.TABLE_USER, 'assigned_user_id'),
+ 'stage_id/id' : self.get_opportunity_status,
+ 'type' : const('opportunity'),
+ 'categ_id/id': call(self.get_category, 'crm.lead', value('opportunity_type')),
+ 'email_from': 'email_from',
+ 'state': map_val('status', self.opp_state),
+ 'description' : 'description',
+ }
+ }
+
+ """
+ import campaign
+ """
+
+ def get_compaign_mapping(self):
+ return {
+ 'model' : 'crm.case.resource.type',
+ 'map' : {
+ 'name': 'name',
+ }
+ }
+
+ """
+ import lead
+ """
+ def get_lead_status(self, sugar_val):
+ fields = ['name', 'type']
+ name = 'lead_' + sugar_val.get('status', '')
+ data = [sugar_val.get('status', ''), 'lead']
+ return self.import_object(fields, data, 'crm.case.stage', self.TABLE_STAGE, name, [('type', '=', 'lead'), ('name', 'ilike', sugar_val.get('status', ''))])
+
+ lead_state = {
+ 'New' : 'draft',
+ 'Assigned':'open',
+ 'In Progress': 'open',
+ 'Recycled': 'cancel',
+ 'Dead': 'done',
+ 'Converted': 'done',
+ }
+
+ def import_lead(self, val):
+ if val.get('opportunity_id'): #if lead is converted into opp, don't import as lead
+ return False
+ if val.get('primary_address_country'):
+ country_id = self.get_all_countries(val.get('primary_address_country'))
+ val['country_id/id'] = country_id
+ val['state_id/id'] = self.get_all_states(val.get('primary_address_state'), country_id)
+ return val
+
+ def get_lead_mapping(self):
+ return {
+ 'model' : 'crm.lead',
+ 'dependencies' : [self.TABLE_COMPAIGN, self.TABLE_USER],
+ 'hook' : self.import_lead,
+ 'map' : {
+ 'name': concat('first_name', 'last_name'),
+ 'contact_name': concat('first_name', 'last_name'),
+ 'description': ppconcat('description', 'refered_by', 'lead_source', 'lead_source_description', 'website', 'email2', 'status_description', 'lead_source_description', 'do_not_call'),
+ 'partner_name': 'account_name',
+ 'email_from': 'email1',
+ 'phone': 'phone_work',
+ 'mobile': 'phone_mobile',
+ 'title/id': call(self.get_job_title, value('salutation')),
+ 'function':'title',
+ 'street': 'primary_address_street',
+ 'street2': 'alt_address_street',
+ 'zip': 'primary_address_postalcode',
+ 'city':'primary_address_city',
+ 'user_id/id' : ref(self.TABLE_USER, 'assigned_user_id'),
+ 'stage_id/id' : self.get_lead_status,
+ 'type' : const('lead'),
+ 'state': map_val('status', self.lead_state) ,
+ 'fax': 'phone_fax',
+ 'referred': 'refered_by',
+ 'optout': 'do_not_call',
+ 'channel_id/id': call(self.get_channel_id, value('lead_source')),
+ 'type_id/id': ref(self.TABLE_COMPAIGN, 'campaign_id'),
+ 'country_id/id': 'country_id/id',
+ 'state_id/id': 'state_id/id',
+ }
+ }
+
+ """
+ import contact
+ """
+
+ def get_email(self, val):
+ email_address = sugar.get_contact_by_email(self.context.get('port'), self.context.get('username'), self.context.get('password'), val.get('email1'))
+ if email_address:
+ return ','.join(email_address)
+
+ def import_contact(self, val):
+ if val.get('primary_address_country'):
+ country_id = self.get_all_countries(val.get('primary_address_country'))
+ state = self.get_all_states(val.get('primary_address_state'), country_id)
+ val['country_id/id'] = country_id
+ val['state_id/id'] = state
+ return val
+
+ def get_contact_mapping(self):
+ return {
+ 'model' : 'res.partner.address',
+ 'dependencies' : [self.TABLE_ACCOUNT],
+ 'hook' : self.import_contact,
+ 'map' : {
+ 'name': concat('first_name', 'last_name'),
+ 'partner_id/id': ref(self.TABLE_ACCOUNT,'account_id'),
+ 'phone': 'phone_work',
+ 'mobile': 'phone_mobile',
+ 'fax': 'phone_fax',
+ 'function': 'title',
+ 'street': 'primary_address_street',
+ 'zip': 'primary_address_postalcode',
+ 'city': 'primary_address_city',
+ 'country_id/id': 'country_id/id',
+ 'state_id/id': 'state_id/id',
+ 'email': self.get_email,
+ 'type': const('contact')
+ }
+ }
+
+ """
+ import Account
+ """
+
+ def get_address_type(self, val, type):
+ if type == 'invoice':
+ type_address = 'billing'
+ else:
+ type_address = 'shipping'
+
+ if type == 'default':
+ map_partner_address = {
+ 'name': 'name',
+ 'type': const('default'),
+ 'email': 'email1'
+ }
+ else:
+ map_partner_address = {
+ 'name': 'name',
+ 'phone': 'phone_office',
+ 'mobile': 'phone_mobile',
+ 'fax': 'phone_fax',
+ 'type': 'type',
+ 'street': type_address + '_address_street',
+ 'zip': type_address +'_address_postalcode',
+ 'city': type_address +'_address_city',
+ 'country_id/id': 'country_id/id',
+ 'type': 'type',
+ }
+
+ if val.get(type_address +'_address_country'):
+ country_id = self.get_all_countries(val.get(type_address +'_address_country'))
+ state = self.get_all_states(val.get(type_address +'_address_state'), country_id)
+ val['country_id/id'] = country_id
+ val['state_id/id'] = state
+
+ val['type'] = type
+ val['id_new'] = val['id'] + '_address_' + type
+ return self.import_object_mapping(map_partner_address, val, 'res.partner.address', self.TABLE_CONTACT, val['id_new'], self.DO_NOT_FIND_DOMAIN)
+
+ def get_partner_address(self, val):
+ address_id=[]
+ type_dict = {'billing_address_street' : 'invoice', 'shipping_address_street' : 'delivery', 'type': 'default'}
+ for key, type_value in type_dict.items():
+ if val.get(key):
+ id = self.get_address_type(val, type_value)
+ address_id.append(id)
+
+ return ','.join(address_id)
+
+ def get_partner_mapping(self):
+ return {
+ 'model' : 'res.partner',
+ 'dependencies' : [self.TABLE_USER],
+ 'map' : {
+ 'name': 'name',
+ 'website': 'website',
+ 'user_id/id': ref(self.TABLE_USER,'assigned_user_id'),
+ 'ref': 'sic_code',
+ 'comment': ppconcat('description', 'employees', 'ownership', 'annual_revenue', 'rating', 'industry', 'ticker_symbol'),
+ 'customer': const('1'),
+ 'supplier': const('0'),
+ 'address/id':'address/id',
+ 'parent_id/id_parent' : 'parent_id',
+ 'address/id' : self.get_partner_address,
+ }
+ }
+
+ """
+ import Employee
+ """
+ def get_ressource(self, val):
+ map_resource = {
+ 'name': concat('first_name', 'last_name'),
+ }
+ return self.import_object_mapping(map_resource, val, 'resource.resource', self.TABLE_RESSOURCE, val['id'], self.DO_NOT_FIND_DOMAIN)
+
+ def get_job_id(self, val):
+ fields = ['name']
+ data = [val.get('title')]
+ return self.import_object(fields, data, 'hr.job', 'hr_job', val.get('title'))
+
+ def get_user_address(self, val):
+ map_user_address = {
+ 'name': concat('first_name', 'last_name'),
+ 'city': 'address_city',
+ 'country_id/id': 'country_id/id',
+ 'state_id/id': 'state_id/id',
+ 'street': 'address_street',
+ 'zip': 'address_postalcode',
+ 'fax': 'phone_fax',
+ 'phone': 'phone_work',
+ 'mobile':'phone_mobile',
+ 'email': 'email1'
+ }
+
+ if val.get('address_country'):
+ country_id = self.get_all_countries(val.get('address_country'))
+ state_id = self.get_all_states(val.get('address_state'), country_id)
+ val['country_id/id'] = country_id
+ val['state_id/id'] = state_id
+
+ return self.import_object_mapping(map_user_address, val, 'res.partner.address', self.TABLE_CONTACT, val['id'], self.DO_NOT_FIND_DOMAIN)
+
+ def get_employee_mapping(self):
+ return {
+ 'model' : 'hr.employee',
+ 'dependencies' : [self.TABLE_USER],
+ 'map' : {
+ 'resource_id/id': self.get_ressource,
+ 'name': concat('first_name', 'last_name'),
+ 'work_phone': 'phone_work',
+ 'mobile_phone': 'phone_mobile',
+ 'user_id/id': ref(self.TABLE_USER, 'id'),
+ 'address_home_id/id': self.get_user_address,
+ 'notes': ppconcat('messenger_type', 'messenger_id', 'description'),
+ 'job_id/id': self.get_job_id,
+ 'work_email' : 'email1',
+ 'coach_id/id_parent' : 'reports_to_id',
+ }
+ }
+
+ """
+ import user
+ """
+ def import_user(self, val):
+ user_obj = self.obj.pool.get('res.users')
+ user_ids = user_obj.search(self.cr, self.uid, [('login', '=', val.get('user_name'))])
+ if user_ids:
+ val['.id'] = str(user_ids[0])
+ else:
+ val['password'] = 'sugarcrm' #default password for all user #TODO needed in documentation
+
+ val['context_lang'] = self.context.get('lang','en_US')
+ return val
+
+ def get_users_department(self, val):
+ dep = val.get('department')
+ fields = ['name']
+ data = [dep]
+ if not dep:
+ return False
+ return self.import_object(fields, data, 'hr.department', 'hr_department_user', dep)
+
+ def get_user_mapping(self):
+ return {
+ 'model' : 'res.users',
+ 'hook' : self.import_user,
+ 'map' : {
+ 'name': concat('first_name', 'last_name'),
+ 'login': value('user_name', fallback='last_name'),
+ 'context_lang' : 'context_lang',
+ 'password' : 'password',
+ '.id' : '.id',
+ 'context_department_id/id': self.get_users_department,
+ 'user_email' : 'email1',
+ }
+ }
+
+
+ def get_mapping(self):
+ return {
+ self.TABLE_USER : self.get_user_mapping(),
+ self.TABLE_EMPLOYEE : self.get_employee_mapping(),
+ self.TABLE_ACCOUNT : self.get_partner_mapping(),
+ self.TABLE_CONTACT : self.get_contact_mapping(),
+ self.TABLE_LEAD : self.get_lead_mapping(),
+ self.TABLE_OPPORTUNITY : self.get_opp_mapping(),
+ self.TABLE_MEETING : self.get_meeting_mapping(),
+ self.TABLE_CALL : self.get_calls_mapping(),
+ self.TABLE_TASK : self.get_task_mapping(),
+ self.TABLE_PROJECT : self.get_project_mapping(),
+ self.TABLE_PROJECT_TASK: self.get_project_task_mapping(),
+ self.TABLE_BUG: self.get_project_issue_mapping(),
+ self.TABLE_CASE: self.get_crm_claim_mapping(),
+ self.TABLE_NOTE: self.get_history_mapping(),
+ self.TABLE_EMAIL: self.get_email_mapping(),
+ self.TABLE_DOCUMENT: self.get_document_mapping(),
+ self.TABLE_COMPAIGN: self.get_compaign_mapping()
+ }
+
+ """
+ Email notification
+ """
+ def get_email_subject(self, result, error=False):
+ if error:
+ return "Sugarcrm data import failed at %s due to an unexpected error" % self.date_ended
+ return "your sugarcrm data were successfully imported at %s" % self.date_ended
+
+ def get_body_header(self, result):
+ return "Sugarcrm import : report of last import"
+
+
+class import_sugarcrm(osv.osv):
+ """Import SugarCRM DATA"""
+
+ _name = "import.sugarcrm"
+ _description = __doc__
+ _columns = {
+ 'username': fields.char('User Name', size=64, required=True),
+ 'password': fields.char('Password', size=24,required=True),
+ 'url' : fields.char('SugarSoap Api url:', size=264, required=True, help="Webservice's url where to get the data.\
+ example : 'http://example.com/sugarcrm/soap.php', or copy the address of your sugarcrm application http://trial.sugarcrm.com/qbquyj4802/index.php?module=Home&action=index"),
+ 'user' : fields.boolean('User', help="Check this box to import sugarCRM Users into OpenERP users, warning if a user with the same login exist in OpenERP, user information will be erase by sugarCRM user information", readonly=True),
+ 'opportunity': fields.boolean('Leads & Opp', help="Check this box to import sugarCRM Leads and Opportunities into OpenERP Leads and Opportunities"),
+ 'contact': fields.boolean('Contacts', help="Check this box to import sugarCRM Contacts into OpenERP addresses"),
+ 'account': fields.boolean('Accounts', help="Check this box to import sugarCRM Accounts into OpenERP partners"),
+ 'employee': fields.boolean('Employee', help="Check this box to import sugarCRM Employees into OpenERP employees"),
+ 'meeting': fields.boolean('Meetings', help="Check this box to import sugarCRM Meetings and Tasks into OpenERP meetings"),
+ 'call': fields.boolean('Calls', help="Check this box to import sugarCRM Calls into OpenERP calls"),
+ 'claim': fields.boolean('Cases', help="Check this box to import sugarCRM Cases into OpenERP claims"),
+ 'email_history': fields.boolean('Email and Note',help="Check this box to import sugarCRM Emails, Notes and Attachments into OpenERP Messages and Attachments"),
+ 'project': fields.boolean('Projects', help="Check this box to import sugarCRM Projects into OpenERP projects"),
+ 'project_task': fields.boolean('Project Tasks', help="Check this box to import sugarCRM Project Tasks into OpenERP tasks"),
+ 'bug': fields.boolean('Bugs', help="Check this box to import sugarCRM Bugs into OpenERP project issues"),
+ 'document': fields.boolean('Documents', help="Check this box to import sugarCRM Documents into OpenERP documents"),
+ 'email_from': fields.char('Notify End Of Import To:', size=128),
+ 'instance_name': fields.char("Instance's Name", size=64, help="Prefix of SugarCRM id to differentiate xml_id of SugarCRM models datas come from different server."),
+ }
+
+ def _get_email_id(self, cr, uid, context=None):
+ return self.pool.get('res.users').browse(cr, uid, uid, context=context).user_email
+
+ def _module_installed(self, cr, uid, model, context=None):
+ module_id = self.pool.get('ir.module.module').search(cr, uid, [('name', '=', model), ('state', "=", "installed")], context=context)
+ return bool(module_id)
+
+ def _project_installed(self, cr, uid, context=None):
+ return self._module_installed(cr,uid,'project',context=context)
+
+ def _crm_claim_installed(self, cr, uid, context=None):
+ return self._module_installed(cr,uid,'crm_claim',context=context)
+
+ def _project_issue_installed(self, cr, uid, context=None):
+ return self._module_installed(cr,uid,'project_issue',context=context)
+
+ def _hr_installed(self, cr, uid, context=None):
+ return self._module_installed(cr,uid,'hr',context=context)
+
+ _defaults = {#to be set to true, but easier for debugging
+ 'user' : True,
+ 'opportunity': True,
+ 'contact' : True,
+ 'account' : True,
+ 'employee' : _hr_installed,
+ 'meeting' : True,
+ 'call' : True,
+ 'claim' : _crm_claim_installed,
+ 'email_history' : True,
+ 'project' : _project_installed,
+ 'project_task': _project_installed,
+ 'bug': _project_issue_installed,
+ 'document': True,
+ 'instance_name': 'sugarcrm',
+ 'email_from': _get_email_id,
+ 'username' : 'admin',
+ 'password' : '',
+ 'url': "http://sugarcrm.example.com/soap.php"
+ }
+
+ def check_url(self, url, context):
+ if not context:
+ context = {}
+ user = context.get('username')
+ password = context.get('password')
+
+ try :
+ sugar.login(user, password, url)
+ return True
+ except Exception:
+ return False
+
+
+ def parse_valid_url(self, context=None):
+ if not context:
+ context = {}
+ url = context.get('url')
+ url_split = str(url).split('/')
+ while len(url_split) >= 3:
+ #3 case, soap.php is already at the end of url should be valid
+ #url end by / or not
+ if url_split[-1] == 'soap.php':
+ url = "/".join(url_split)
+ elif url_split[-1] == '':
+ url = "/".join(url_split) + "soap.php"
+ else:
+ url = "/".join(url_split) + "/soap.php"
+
+ if self.check_url(url, context):
+ return url
+ else:
+ url_split = url_split[:-1] #take parent folder
+ return url
+
+ def get_key(self, cr, uid, ids, context=None):
+ """Select Key as For which Module data we want import data."""
+ if not context:
+ context = {}
+ key_list = []
+ module = {}
+ for current in self.browse(cr, uid, ids, context):
+ context.update({'username': current.username, 'password': current.password, 'url': current.url, 'email_user': current.email_from or False, 'instance_name': current.instance_name or False})
+ if current.user:
+ key_list.append('Users')
+ if current.contact:
+ key_list.append('Contacts')
+ if current.account:
+ key_list.append('Accounts')
+ if current.opportunity:
+ key_list.append('Leads')
+ key_list.append('Opportunities')
+ if current.employee:
+ key_list.append('Employees')
+ module.update({'Employees':'hr'})
+ if current.meeting:
+ key_list.append('Meetings')
+ if current.call:
+ key_list.append('Calls')
+ if current.claim:
+ key_list.append('Cases')
+ module.update({'Cases':'crm_claim'})
+ if current.email_history:
+ key_list.append('Emails')
+ key_list.append('Notes')
+ if current.project:
+ key_list.append('Project')
+ module.update({'Project':'project'})
+ if current.project_task:
+ key_list.append('ProjectTask')
+ module.update({'ProjectTask':'project'})
+ if current.bug:
+ key_list.append('Bugs')
+ module.update({'Bugs':'project_issue'})
+ if current.document:
+ key_list.append('Documents')
+ return key_list,module
+
+
+ def do_import_all(self, cr, uid, *args):
+ """
+ scheduler Method
+ """
+ context = {'username': args[4], 'password': args[5], 'url': args[3], 'instance_name': args[3]}
+ imp = sugar_import(self, cr, uid, args[2], "import_sugarcrm", [args[1]], context)
+ imp.set_table_list(args[0])
+ imp.start()
+ return True
+
+ def import_from_scheduler_all(self, cr, uid, ids, context=None):
+ keys, module_list = self.get_key(cr, uid, ids, context)
+ if not keys:
+ raise osv.except_osv(_('Warning !'), _('Select Module to Import.'))
+ key_list = module_list.keys()
+ for module in key_list :
+ module = module_list[module]
+ state = self.get_all(cr,uid,module,context=context)
+ if state == False:
+ keys = ', '.join(key_list)
+ raise osv.except_osv(_('Error !!'), _("%s data required %s Module to be installed, Please install %s module") %(keys,module,module))
+ cron_obj = self.pool.get('ir.cron')
+ url = self.parse_valid_url(context)
+ args = (keys,context.get('email_user'), context.get('instance_name'), url, context.get('username'), context.get('password') )
+ new_create_id = cron_obj.create(cr, uid, {'name': 'Import SugarCRM datas','interval_type': 'hours','interval_number': 1, 'numbercall': -1,'model': 'import.sugarcrm','function': 'do_import_all', 'args': args, 'active': False})
+ return {
+ 'name': 'SugarCRM Scheduler',
+ 'view_type': 'form',
+ 'view_mode': 'form,tree',
+ 'res_model': 'ir.cron',
+ 'res_id': new_create_id,
+ 'type': 'ir.actions.act_window',
+ }
+
+ def import_all(self, cr, uid, ids, context=None):
+
+# """Import all sugarcrm data into openerp module"""
+ keys, module_list = self.get_key(cr, uid, ids, context)
+ if not keys:
+ raise osv.except_osv(_('Warning !'), _('Select Module to Import.'))
+ key_list = module_list.keys()
+ for module in key_list :
+ module = module_list[module]
+ state = self._module_installed(cr,uid,module,context=context)
+ if state == False:
+ keys = ', '.join(key_list)
+ raise osv.except_osv(_('Error !!'), _("%s data required %s Module to be installed, Please install %s module") %(keys,module,module))
+ url = self.parse_valid_url(context)
+ context.update({'url': url})
+ imp = sugar_import(self, cr, uid, context.get('instance_name'), "import_sugarcrm", [context.get('email_user')], context)
+ imp.set_table_list(keys)
+ imp.start()
+ obj_model = self.pool.get('ir.model.data')
+ model_data_ids = obj_model.search(cr,uid,[('model','=','ir.ui.view'),('name','=','import.message.form')])
+ resource_id = obj_model.read(cr, uid, model_data_ids, fields=['res_id'])
+ return {
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'import.message',
+ 'views': [(resource_id,'form')],
+ 'type': 'ir.actions.act_window',
+ 'target': 'new',
+ }
+
+import_sugarcrm()
diff --git a/addons/import_sugarcrm/import_sugarcrm_view.xml b/addons/import_sugarcrm/import_sugarcrm_view.xml
new file mode 100644
index 00000000000..1f12a8fde57
--- /dev/null
+++ b/addons/import_sugarcrm/import_sugarcrm_view.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+ import.sugarcrm.form
+ import.sugarcrm
+ form
+
+
+
+
+
+
+ mail.message.tree
+ mail.message
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mail.message.search
+ mail.message
+ search
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Messages
+ mail.message
+ form
+ tree,form
+ [('email_from', '!=', False)]
+ {'search_default_outgoing':1}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
new file mode 100644
index 00000000000..fa785c3517c
--- /dev/null
+++ b/addons/mail/mail_thread.py
@@ -0,0 +1,463 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2009-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import time
+import tools
+import base64
+import email
+from email.utils import parsedate
+
+import logging
+import xmlrpclib
+from osv import osv, fields
+from tools.translate import _
+from mail_message import decode, to_email
+
+_logger = logging.getLogger('mail')
+
+class mail_thread(osv.osv):
+ '''Mixin model, meant to be inherited by any model that needs to
+ act as a discussion topic on which messages can be attached.
+ Public methods are prefixed with ``message_`` in order to avoid
+ name collisions with methods of the models that will inherit
+ from this mixin.
+
+ ``mail.thread`` adds a one2many of mail.messages, acting as the
+ thread's history, and a few methods that may be overridden to
+ implement model-specific behavior upon arrival of new messages.
+
+ Inheriting classes are not required to implement any method, as the
+ default implementation will work for any model. However it is common
+ to override at least the ``message_new`` and ``message_update``
+ methods (calling ``super``) to add model-specific behavior at
+ creation and update of a thread.
+
+ '''
+ _name = 'mail.thread'
+ _description = 'Email Thread'
+
+ _columns = {
+ 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', readonly=True),
+ }
+
+ def message_thread_followers(self, cr, uid, ids, context=None):
+ """Returns a list of email addresses of the people following
+ this thread, including the sender of each mail, and the
+ people who were in CC of the messages, if any.
+ """
+ res = {}
+ if isinstance(ids, (str, int, long)):
+ ids = [long(ids)]
+ for thread in self.browse(cr, uid, ids, context=context):
+ l = set()
+ for message in thread.message_ids:
+ l.add((message.user_id and message.user_id.user_email) or '')
+ l.add(message.email_from or '')
+ l.add(message.email_cc or '')
+ res[thread.id] = filter(None, l)
+ return res
+
+ def copy(self, cr, uid, id, default=None, context=None):
+ """Overrides default copy method to empty the thread of
+ messages attached to this record, as the copied object
+ will have its own thread and does not have to share it.
+ """
+ if default is None:
+ default = {}
+ default.update({
+ 'message_ids': [],
+ })
+ return super(mail_thread, self).copy(cr, uid, id, default, context=context)
+
+ def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
+ """Called by ``message_process`` when a new message is received
+ for a given thread model, if the message did not belong to
+ an existing thread.
+ The default behavior is to create a new record of the corresponding
+ model (based on some very basic info extracted from the message),
+ then attach the message to the newly created record
+ (by calling ``message_append_dict``).
+ Additional behavior may be implemented by overriding this method.
+
+ :param dict msg_dict: a map containing the email details and
+ attachments. See ``message_process`` and
+ ``mail.message.parse`` for details.
+ :param dict custom_values: optional dictionary of additional
+ field values to pass to create()
+ when creating the new thread record.
+ Be careful, these values may override
+ any other values coming from the message.
+ :param dict context: if a ``thread_model`` value is present
+ in the context, its value will be used
+ to determine the model of the record
+ to create (instead of the current model).
+ :rtype: int
+ :return: the id of the newly created thread object
+ """
+ if context is None:
+ context = {}
+ model = context.get('thread_model') or self._name
+ model_pool = self.pool.get(model)
+ fields = model_pool.fields_get(cr, uid, context=context)
+ data = model_pool.default_get(cr, uid, fields, context=context)
+ if 'name' in fields and not data.get('name'):
+ data['name'] = msg_dict.get('from','')
+ if custom_values and isinstance(custom_values, dict):
+ data.update(custom_values)
+ res_id = model_pool.create(cr, uid, data, context=context)
+ self.message_append_dict(cr, uid, [res_id], msg_dict, context=context)
+ return res_id
+
+ def message_update(self, cr, uid, ids, msg_dict, vals={}, default_act=None, context=None):
+ """Called by ``message_process`` when a new message is received
+ for an existing thread. The default behavior is to create a
+ new mail.message in the given thread (by calling
+ ``message_append_dict``)
+ Additional behavior may be implemented by overriding this
+ method.
+
+ :param dict msg_dict: a map containing the email details and
+ attachments. See ``message_process`` and
+ ``mail.message.parse()`` for details.
+ :param dict context: if a ``thread_model`` value is present
+ in the context, its value will be used
+ to determine the model of the thread to
+ update (instead of the current model).
+ """
+ return self.message_append_dict(cr, uid, ids, msg_dict, context=context)
+
+ def message_append(self, cr, uid, threads, subject, body_text=None, email_to=False,
+ email_from=False, email_cc=None, email_bcc=None, reply_to=None,
+ email_date=None, message_id=False, references=None,
+ attachments=None, body_html=None, subtype=None, headers=None,
+ original=None, context=None):
+ """Creates a new mail.message attached to the current mail.thread,
+ containing all the details passed as parameters. All attachments
+ will be attached to the thread record as well as to the actual
+ message.
+ If only the ``threads`` and ``subject`` parameters are provided,
+ a *event log* message is created, without the usual envelope
+ attributes (sender, recipients, etc.).
+
+ :param threads: list of thread ids, or list of browse_records representing
+ threads to which a new message should be attached
+ :param subject: subject of the message, or description of the event if this
+ is an *event log* entry.
+ :param email_to: Email-To / Recipient address
+ :param email_from: Email From / Sender address if any
+ :param email_cc: Comma-Separated list of Carbon Copy Emails To addresse if any
+ :param email_bcc: Comma-Separated list of Blind Carbon Copy Emails To addresses if any
+ :param reply_to: reply_to header
+ :param email_date: email date string if different from now, in server timezone
+ :param message_id: optional email identifier
+ :param references: optional email references
+ :param body_text: plaintext contents of the mail or log message
+ :param body_html: html contents of the mail or log message
+ :param subtype: optional type of message: 'plain' or 'html', corresponding to the main
+ body contents (body_text or body_html).
+ :param headers: mail headers to store
+ :param dict attachments: map of attachment filenames to binary contents, if any.
+ :param str original: optional full source of the RFC2822 email, for reference
+ :param dict context: if a ``thread_model`` value is present
+ in the context, its value will be used
+ to determine the model of the thread to
+ update (instead of the current model).
+ """
+ if context is None:
+ context = {}
+ if attachments is None:
+ attachments = {}
+
+ if email_date:
+ edate = parsedate(email_date)
+ if edate is not None:
+ email_date = time.strftime('%Y-%m-%d %H:%M:%S', edate)
+
+ if all(isinstance(thread_id, (int, long)) for thread_id in threads):
+ model = context.get('thread_model') or self._name
+ model_pool = self.pool.get(model)
+ threads = model_pool.browse(cr, uid, threads, context=context)
+
+ ir_attachment = self.pool.get('ir.attachment')
+ mail_message = self.pool.get('mail.message')
+
+ for thread in threads:
+ to_attach = []
+ for fname, fcontent in attachments.items():
+ if isinstance(fcontent, unicode):
+ fcontent = fcontent.encode('utf-8')
+ data_attach = {
+ 'name': fname,
+ 'datas': base64.b64encode(str(fcontent)),
+ 'datas_fname': fname,
+ 'description': _('Mail attachment'),
+ 'res_model': thread._name,
+ 'res_id': thread.id,
+ }
+ to_attach.append(ir_attachment.create(cr, uid, data_attach, context=context))
+
+ partner_id = hasattr(thread, 'partner_id') and (thread.partner_id and thread.partner_id.id or False) or False
+ if not partner_id and thread._name == 'res.partner':
+ partner_id = thread.id
+ data = {
+ 'subject': subject,
+ 'user_id': uid,
+ 'model' : thread._name,
+ 'partner_id': partner_id,
+ 'res_id': thread.id,
+ 'date': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'message_id': message_id,
+ 'body_text': body_text or (hasattr(thread, 'description') and thread.description or False),
+ 'attachment_ids': [(6, 0, to_attach)],
+ 'state' : 'received',
+ }
+
+ if email_from:
+ for param in (email_to, email_cc, email_bcc):
+ if isinstance(param, list):
+ param = ", ".join(param)
+ data = {
+ 'subject': subject or _('History'),
+ 'user_id': uid,
+ 'model' : thread._name,
+ 'res_id': thread.id,
+ 'date': email_date or time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'body_text': body_text,
+ 'email_to': email_to,
+ 'email_from': email_from or \
+ (hasattr(thread, 'user_id') and thread.user_id and thread.user_id.user_email),
+ 'email_cc': email_cc,
+ 'email_bcc': email_bcc,
+ 'partner_id': partner_id,
+ 'references': references,
+ 'message_id': message_id,
+ 'attachment_ids': [(6, 0, to_attach)],
+ 'state' : 'received',
+ 'body_html': body_html,
+ 'subtype': subtype,
+ 'headers': headers,
+ 'reply_to': reply_to,
+ 'original': original,
+ }
+ mail_message.create(cr, uid, data, context=context)
+ return True
+
+ def message_append_dict(self, cr, uid, ids, msg_dict, context=None):
+ """Creates a new mail.message attached to the given threads (``ids``),
+ with the contents of ``msg_dict``, by calling ``message_append``
+ with the mail details. All attachments in msg_dict will be
+ attached to the object record as well as to the actual
+ mail message.
+
+ :param dict msg_dict: a map containing the email details and
+ attachments. See ``message_process()`` and
+ ``mail.message.parse()`` for details on
+ the dict structure.
+ :param dict context: if a ``thread_model`` value is present
+ in the context, its value will be used
+ to determine the model of the thread to
+ update (instead of the current model).
+ """
+ return self.message_append(cr, uid, ids,
+ subject = msg_dict.get('subject'),
+ body_text = msg_dict.get('body_text'),
+ email_to = msg_dict.get('to'),
+ email_from = msg_dict.get('from'),
+ email_cc = msg_dict.get('cc'),
+ email_bcc = msg_dict.get('bcc'),
+ reply_to = msg_dict.get('reply'),
+ email_date = msg_dict.get('date'),
+ message_id = msg_dict.get('message-id'),
+ references = msg_dict.get('references')\
+ or msg_dict.get('in-reply-to'),
+ attachments = msg_dict.get('attachments'),
+ body_html= msg_dict.get('body_html'),
+ subtype = msg_dict.get('subtype'),
+ headers = msg_dict.get('headers'),
+ original = msg_dict.get('original'),
+ context = context)
+
+
+ def message_process(self, cr, uid, model, message, custom_values=None,
+ save_original=False, strip_attachments=False,
+ context=None):
+ """Process an incoming RFC2822 email message related to the
+ given thread model, relying on ``mail.message.parse()``
+ for the parsing operation, and then calling ``message_new``
+ (if the thread record did not exist) or ``message_update``
+ (if it did), then calling ``message_forward`` to automatically
+ notify other people that should receive this message.
+
+ :param string model: the thread model for which a new message
+ must be processed
+ :param message: source of the RFC2822 mail
+ :type message: string or xmlrpclib.Binary
+ :type dict custom_values: optional dictionary of field values
+ to pass to ``message_new`` if a new
+ record needs to be created. Ignored
+ if the thread record already exists.
+ :param bool save_original: whether to keep a copy of the original
+ email source attached to the message after it is imported.
+ :param bool strip_attachments: whether to strip all attachments
+ before processing the message, in order to save some space.
+ """
+ # extract message bytes - we are forced to pass the message as binary because
+ # we don't know its encoding until we parse its headers and hence can't
+ # convert it to utf-8 for transport between the mailgate script and here.
+ if isinstance(message, xmlrpclib.Binary):
+ message = str(message.data)
+
+ model_pool = self.pool.get(model)
+ if self._name != model:
+ if context is None: context = {}
+ context.update({'thread_model': model})
+
+ mail_message = self.pool.get('mail.message')
+ res_id = False
+
+ # Parse Message
+ # Warning: message_from_string doesn't always work correctly on unicode,
+ # we must use utf-8 strings here :-(
+ if isinstance(message, unicode):
+ message = message.encode('utf-8')
+ msg_txt = email.message_from_string(message)
+ msg = mail_message.parse_message(msg_txt, save_original=save_original)
+
+ if strip_attachments and 'attachments' in msg:
+ del msg['attachments']
+
+ # Create New Record into particular model
+ def create_record(msg):
+ if hasattr(model_pool, 'message_new'):
+ return model_pool.message_new(cr, uid, msg,
+ custom_values,
+ context=context)
+ res_id = False
+ if msg.get('references') or msg.get('in-reply-to'):
+ references = msg.get('references') or msg.get('in-reply-to')
+ if '\r\n' in references:
+ references = references.split('\r\n')
+ else:
+ references = references.split(' ')
+ for ref in references:
+ ref = ref.strip()
+ res_id = tools.reference_re.search(ref)
+ if res_id:
+ res_id = res_id.group(1)
+ else:
+ res_id = tools.res_re.search(msg['subject'])
+ if res_id:
+ res_id = res_id.group(1)
+ if res_id:
+ res_id = int(res_id)
+ if model_pool.exists(cr, uid, res_id):
+ if hasattr(model_pool, 'message_update'):
+ model_pool.message_update(cr, uid, [res_id], msg, {}, context=context)
+ else:
+ # referenced thread was not found, we'll have to create a new one
+ res_id = False
+ if not res_id:
+ res_id = create_record(msg)
+ #To forward the email to other followers
+ self.message_forward(cr, uid, model, [res_id], msg_txt, context=context)
+ return res_id
+
+ # for backwards-compatibility with old scripts
+ process_email = message_process
+
+ def message_forward(self, cr, uid, model, thread_ids, msg, email_error=False, context=None):
+ """Sends an email to all people following the given threads.
+ The emails are forwarded immediately, not queued for sending,
+ and not archived.
+
+ :param str model: thread model
+ :param list thread_ids: ids of the thread records
+ :param msg: email.message.Message object to forward
+ :param email_error: optional email address to notify in case
+ of any delivery error during the forward.
+ :return: True
+ """
+ model_pool = self.pool.get(model)
+ smtp_server_obj = self.pool.get('ir.mail_server')
+ mail_message = self.pool.get('mail.message')
+ for res in model_pool.browse(cr, uid, thread_ids, context=context):
+ if hasattr(model_pool, 'message_thread_followers'):
+ followers = model_pool.message_thread_followers(cr, uid, [res.id])[res.id]
+ else:
+ followers = self.message_thread_followers(cr, uid, [res.id])[res.id]
+ message_followers_emails = to_email(','.join(filter(None, followers)))
+ message_recipients = to_email(','.join(filter(None,
+ [decode(msg['from']),
+ decode(msg['to']),
+ decode(msg['cc'])])))
+ forward_to = [i for i in message_followers_emails if (i and (i not in message_recipients))]
+ if forward_to:
+ # TODO: we need an interface for this for all types of objects, not just leads
+ if hasattr(res, 'section_id'):
+ del msg['reply-to']
+ msg['reply-to'] = res.section_id.reply_to
+
+ smtp_from, = to_email(msg['from'])
+ msg['from'] = smtp_from
+ msg['to'] = ", ".join(forward_to)
+ msg['message-id'] = tools.generate_tracking_message_id(res.id)
+ if not smtp_server_obj.send_email(cr, uid, msg) and email_error:
+ subj = msg['subject']
+ del msg['subject'], msg['to'], msg['cc'], msg['bcc']
+ msg['subject'] = _('[OpenERP-Forward-Failed] %s') % subj
+ msg['to'] = email_error
+ smtp_server_obj.send_email(cr, uid, msg)
+ return True
+
+ def message_partner_by_email(self, cr, uid, email, context=None):
+ """Attempts to return the id of a partner address matching
+ the given ``email``, and the corresponding partner id.
+ Can be used by classes using the ``mail.thread`` mixin
+ to lookup the partner and use it in their implementation
+ of ``message_new`` to link the new record with a
+ corresponding partner.
+ The keys used in the returned dict are meant to map
+ to usual names for relationships towards a partner
+ and one of its addresses.
+
+ :param email: email address for which a partner
+ should be searched for.
+ :rtype: dict
+ :return: a map of the following form::
+
+ { 'partner_address_id': id or False,
+ 'partner_id': pid or False }
+ """
+ address_pool = self.pool.get('res.partner.address')
+ res = {
+ 'partner_address_id': False,
+ 'partner_id': False
+ }
+ if email:
+ email = to_email(email)[0]
+ address_ids = address_pool.search(cr, uid, [('email', '=', email)])
+ if address_ids:
+ address = address_pool.browse(cr, uid, address_ids[0])
+ res['partner_address_id'] = address_ids[0]
+ res['partner_id'] = address.partner_id.id
+ return res
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml
new file mode 100644
index 00000000000..bd10a72faf2
--- /dev/null
+++ b/addons/mail/mail_thread_view.xml
@@ -0,0 +1,72 @@
+
+
+
+
+ mail.thread.form
+ mail.thread
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mail.thread.tree
+ mail.thread
+ tree
+
+
+
+
+
+
+
+
+
+ Email Threads
+ mail.thread
+ tree,form
+ form
+
+
+
+
+ tree
+
+
+
+
+
+ form
+
+
+
+
+
+
diff --git a/addons/mail_gateway/res_partner.py b/addons/mail/res_partner.py
similarity index 86%
rename from addons/mail_gateway/res_partner.py
rename to addons/mail/res_partner.py
index c6f41ff0414..637922ff75c 100644
--- a/addons/mail_gateway/res_partner.py
+++ b/addons/mail/res_partner.py
@@ -19,17 +19,16 @@
#
##############################################################################
-from osv import fields,osv
+from osv import osv
+from osv import fields
class res_partner(osv.osv):
""" Inherits partner and adds CRM information in the partner form """
_inherit = 'res.partner'
_columns = {
- 'emails': fields.one2many('mailgate.message', 'partner_id',\
- 'Emails', readonly=True, domain=[('history','=',True)]),
+ 'emails': fields.one2many('mail.message', 'partner_id', 'Emails', readonly=True, domain=[('email_from','!=',False)]),
}
res_partner()
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/res_partner_view.xml b/addons/mail/res_partner_view.xml
new file mode 100644
index 00000000000..21da7d735b7
--- /dev/null
+++ b/addons/mail/res_partner_view.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ res.partner.crm.history.inherit1
+ res.partner
+ form
+
+
+
+ False
+
+
+
+
+ res.partner.emails.info.inherit
+ res.partner
+ form
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/mail/scripts/__init__.py b/addons/mail/scripts/__init__.py
new file mode 100644
index 00000000000..3b6a8c7e4b0
--- /dev/null
+++ b/addons/mail/scripts/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2009-2010 OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import openerp_mailgate
\ No newline at end of file
diff --git a/addons/mail/scripts/openerp_mailgate/__init__.py b/addons/mail/scripts/openerp_mailgate/__init__.py
new file mode 100644
index 00000000000..de2e2bf390d
--- /dev/null
+++ b/addons/mail/scripts/openerp_mailgate/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2009-2010 OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import openerp_mailgate
diff --git a/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py b/addons/mail/scripts/openerp_mailgate/openerp_mailgate.py
similarity index 86%
rename from addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py
rename to addons/mail/scripts/openerp_mailgate/openerp_mailgate.py
index bcff408ef46..68ae20d35e3 100755
--- a/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py
+++ b/addons/mail/scripts/openerp_mailgate/openerp_mailgate.py
@@ -46,13 +46,14 @@ class DefaultConfig(object):
OPENERP_PORT = 8069
OPENERP_DEFAULT_DATABASE = 'openerp'
MAIL_ERROR = 'error@example.com'
- MAIL_SERVER = 'localhost'
- MAIL_ADMINS = ('admin@example.com',)
+ MAIL_SERVER = 'smtp.example.com'
+ MAIL_SERVER_PORT = 25
+ MAIL_ADMINS = ('info@example.com',)
config = DefaultConfig()
-def send_mail(_from_, to_, subject, text, files=None, server=config.MAIL_SERVER):
+def send_mail(_from_, to_, subject, text, files=None, server=config.MAIL_SERVER, port=config.MAIL_SERVER_PORT):
assert isinstance(to_, (list, tuple))
if files is None:
@@ -74,7 +75,7 @@ def send_mail(_from_, to_, subject, text, files=None, server=config.MAIL_SERVER)
% file_name)
msg.attach(part)
- smtp = smtplib.SMTP(server)
+ smtp = smtplib.SMTP(server, port=port)
smtp.sendmail(_from_, to_, msg.as_string() )
smtp.close()
@@ -104,16 +105,15 @@ class EmailParser(object):
self.email_default = email_default
- def parse(self, message, custom_values=None):
- if custom_values is None:
- custom_values = {}
+ def parse(self, message, custom_values=None, save_original=None):
# pass message as bytes because we don't know its encoding until we parse its headers
# and hence can't convert it to utf-8 for transport
- self.rpc('email.server.tools',
- 'process_email',
- self.model,
- xmlrpclib.Binary(message),
- custom_values)
+ res_id = self.rpc('mail.thread',
+ 'message_process',
+ self.model,
+ xmlrpclib.Binary(message),
+ custom_values or {},
+ save_original or False)
def configure_parser():
parser = optparse.OptionParser(usage='usage: %prog [options]', version='%prog v1.1')
@@ -145,6 +145,10 @@ def configure_parser():
parser.add_option("--custom-values", dest="custom_values",
help="Add Custom Values to the object",
default=None)
+ parser.add_option("-s", dest="save_original",
+ action="store_true",
+ help="Attach a copy of original email to the message entry",
+ default=False)
return parser
@@ -171,10 +175,11 @@ def main():
try:
custom_values = dict(eval(options.custom_values or {} ))
except:
- pass
+ import traceback
+ traceback.print_exc()
try:
- email_parser.parse(msg_txt, custom_values)
+ email_parser.parse(msg_txt, custom_values, options.save_original or False)
except Exception:
msg = '\n'.join([
'parameters',
@@ -191,6 +196,7 @@ def main():
config.MAIL_ADMINS,
subject, msg, files=[('message.txt', msg_txt)]
)
+ sys.stderr.write("Failed to deliver email to OpenERP Server, sending error notification to %s\n" % config.MAIL_ADMINS)
if __name__ == '__main__':
main()
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
new file mode 100644
index 00000000000..b508cd68d83
--- /dev/null
+++ b/addons/mail/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+"access_mail_message","mail.message","model_mail_message",,1,0,0,0
+"access_mail_thread","mail.thread","model_mail_thread",,1,0,0,0
diff --git a/addons/mail/wizard/__init__.py b/addons/mail/wizard/__init__.py
new file mode 100644
index 00000000000..88b9581f68e
--- /dev/null
+++ b/addons/mail/wizard/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import mail_compose_message
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
new file mode 100644
index 00000000000..0bfaf06890a
--- /dev/null
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -0,0 +1,272 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2010-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import re
+
+import tools
+from mail.mail_message import to_email
+from osv import osv
+from osv import fields
+from tools.safe_eval import safe_eval as eval
+from tools.safe_eval import literal_eval
+from tools.translate import _
+
+# main mako-like expression pattern
+EXPRESSION_PATTERN = re.compile('(\$\{.+?\})')
+
+class mail_compose_message(osv.osv_memory):
+ """Generic E-mail composition wizard. This wizard is meant to be inherited
+ at model and view level to provide specific wizard features.
+
+ The behavior of the wizard can be modified through the use of context
+ parameters, among which are:
+
+ * mail.compose.message.mode: if set to 'reply', the wizard is in
+ reply mode and pre-populated with the original quote.
+ If set to 'mass_mail', the wizard is in mass mailing
+ where the mail details can contain template placeholders
+ that will be merged with actual data before being sent
+ to each recipient. Recipients will be derived from the
+ records determined via ``context['active_model']`` and
+ ``context['active_ids']``.
+ * active_model: model name of the document to which the mail being
+ composed is related
+ * active_id: id of the document to which the mail being composed is
+ related, or id of the message to which user is replying,
+ in case ``mail.compose.message.mode == 'reply'``
+ * active_ids: ids of the documents to which the mail being composed is
+ related, in case ``mail.compose.message.mode == 'mass_mail'``.
+ """
+ _name = 'mail.compose.message'
+ _inherit = 'mail.message.common'
+ _description = 'E-mail composition wizard'
+
+ def default_get(self, cr, uid, fields, context=None):
+ """Overridden to provide specific defaults depending on the context
+ parameters.
+
+ :param dict context: several context values will modify the behavior
+ of the wizard, cfr. the class description.
+ """
+ if context is None:
+ context = {}
+ result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
+ vals = {}
+ reply_mode = context.get('mail.compose.message.mode') == 'reply'
+ if (not reply_mode) and context.get('active_model') and context.get('active_id'):
+ # normal mode when sending an email related to any document, as specified by
+ # active_model and active_id in context
+ vals = self.get_value(cr, uid, context.get('active_model'), context.get('active_id'), context)
+ elif reply_mode and context.get('active_id'):
+ # reply mode, consider active_id is the ID of a mail.message to which we're
+ # replying
+ vals = self.get_message_data(cr, uid, int(context['active_id']), context)
+ else:
+ # default mode
+ result['model'] = context.get('active_model', False)
+ if vals:
+ for field in fields:
+ result.update({field : vals.get(field, False)})
+
+ # link to model and record if not done yet
+ if not result.get('model') or not result.get('res_id'):
+ active_model = context.get('active_model')
+ res_id = context.get('active_id')
+ if active_model and active_model not in (self._name, 'mail.message'):
+ result['model'] = active_model
+ if res_id:
+ result['res_id'] = res_id
+
+ # Try to provide default email_from if not specified yet
+ if not result.get('email_from'):
+ current_user = self.pool.get('res.users').browse(cr, uid, uid, context)
+ result['email_from'] = current_user.user_email or False
+ return result
+
+ _columns = {
+ 'attachment_ids': fields.many2many('ir.attachment','email_message_send_attachment_rel', 'wizard_id', 'attachment_id', 'Attachments'),
+ 'auto_delete': fields.boolean('Auto Delete', help="Permanently delete emails after sending"),
+ 'filter_id': fields.many2one('ir.filters', 'Filters'),
+ }
+
+ def get_value(self, cr, uid, model, res_id, context=None):
+ """Returns a defaults-like dict with initial values for the composition
+ wizard when sending an email related to the document record identified
+ by ``model`` and ``res_id``.
+
+ The default implementation returns an empty dictionary, and is meant
+ to be overridden by subclasses.
+
+ :param str model: model name of the document record this mail is related to.
+ :param int res_id: id of the document record this mail is related to.
+ :param dict context: several context values will modify the behavior
+ of the wizard, cfr. the class description.
+ """
+ return {}
+
+ def get_message_data(self, cr, uid, message_id, context=None):
+ """Returns a defaults-like dict with initial values for the composition
+ wizard when replying to the given message (e.g. including the quote
+ of the initial message, and the correct recipient).
+ Should not be called unless ``context['mail.compose.message.mode'] == 'reply'``.
+
+ :param int message_id: id of the mail.message to which the user
+ is replying.
+ :param dict context: several context values will modify the behavior
+ of the wizard, cfr. the class description.
+ When calling this method, the ``'mail'`` value
+ in the context should be ``'reply'``.
+ """
+ if context is None:
+ context = {}
+ result = {}
+ mail_message = self.pool.get('mail.message')
+ if message_id:
+ message_data = mail_message.browse(cr, uid, message_id, context)
+ subject = tools.ustr(message_data.subject or '')
+ # we use the plain text version of the original mail, by default,
+ # as it is easier to quote than the HTML version.
+ # XXX TODO: make it possible to switch to HTML on the fly
+ current_user = self.pool.get('res.users').browse(cr, uid, uid, context)
+ body = message_data.body_text or current_user.signature
+ if context.get('mail.compose.message.mode') == 'reply':
+ sent_date = _('On %(date)s, ') % {'date': message_data.date} if message_data.date else ''
+ sender = _('%(sender_name)s wrote:') % {'sender_name': tools.ustr(message_data.email_from or _('You'))}
+ quoted_body = '> %s' % tools.ustr(body.replace('\n', "\n> ") or '')
+ body = '\n'.join(["\n", (sent_date + sender), quoted_body])
+ body += "\n" + current_user.signature
+ re_prefix = _("Re:")
+ if not (subject.startswith('Re:') or subject.startswith(re_prefix)):
+ subject = "%s %s" % (re_prefix, subject)
+ result.update({
+ 'subtype' : 'plain', # default to the text version due to quoting
+ 'body_text' : body,
+ 'subject' : subject,
+ 'attachment_ids' : [],
+ 'model' : message_data.model or False,
+ 'res_id' : message_data.res_id or False,
+ 'email_from' : current_user.user_email or message_data.email_to or False,
+ 'email_to' : message_data.reply_to or message_data.email_from or False,
+ 'email_cc' : message_data.email_cc or False,
+ 'user_id' : uid,
+
+ # pass msg-id and references of mail we're replying to, to construct the
+ # new ones later when sending
+ 'message_id' : message_data.message_id or False,
+ 'references' : message_data.references and tools.ustr(message_data.references) or False,
+ })
+ return result
+
+ def send_mail(self, cr, uid, ids, context=None):
+ '''Process the wizard contents and proceed with sending the corresponding
+ email(s), rendering any template patterns on the fly if needed.
+ If the wizard is in mass-mail mode (context['mail.compose.message.mode'] is
+ set to ``'mass_mail'``), the resulting email(s) are scheduled for being
+ sent the next time the mail.message scheduler runs, or the next time
+ ``mail.message.process_email_queue`` is called.
+ Otherwise the new message is sent immediately.
+
+ :param dict context: several context values will modify the behavior
+ of the wizard, cfr. the class description.
+ '''
+ if context is None:
+ context = {}
+ mail_message = self.pool.get('mail.message')
+ for mail in self.browse(cr, uid, ids, context=context):
+ attachment = {}
+ for attach in mail.attachment_ids:
+ attachment[attach.datas_fname] = attach.datas
+ references = None
+ headers = {}
+
+ body = mail.body_html if mail.subtype == 'html' else mail.body_text
+
+ # Reply Email
+ if context.get('mail.compose.message.mode') == 'reply' and mail.message_id:
+ references = (mail.references or '') + " " + mail.message_id
+ headers['In-Reply-To'] = mail.message_id
+
+ if context.get('mail.compose.message.mode') == 'mass_mail':
+ # Mass mailing: must render the template patterns
+ if context.get('active_ids') and context.get('active_model'):
+ active_ids = context['active_ids']
+ active_model = context['active_model']
+ else:
+ active_model = mail.model
+ active_model_pool = self.pool.get(active_model)
+ active_ids = active_model_pool.search(cr, uid, literal_eval(mail.filter_id.domain), context=literal_eval(mail.filter_id.context))
+
+ for active_id in active_ids:
+ subject = self.render_template(cr, uid, mail.subject, active_model, active_id)
+ rendered_body = self.render_template(cr, uid, body, active_model, active_id)
+ email_from = self.render_template(cr, uid, mail.email_from, active_model, active_id)
+ email_to = self.render_template(cr, uid, mail.email_to, active_model, active_id)
+ email_cc = self.render_template(cr, uid, mail.email_cc, active_model, active_id)
+ email_bcc = self.render_template(cr, uid, mail.email_bcc, active_model, active_id)
+ reply_to = self.render_template(cr, uid, mail.reply_to, active_model, active_id)
+
+ # in mass-mailing mode we only schedule the mail for sending, it will be
+ # processed as soon as the mail scheduler runs.
+ mail_message.schedule_with_attach(cr, uid, email_from, to_email(email_to), subject, rendered_body,
+ model=mail.model, email_cc=to_email(email_cc), email_bcc=to_email(email_bcc), reply_to=reply_to,
+ attachments=attachment, references=references, res_id=int(mail.res_id),
+ subtype=mail.subtype, headers=headers, context=context)
+ else:
+ # normal mode - no mass-mailing
+ msg_id = mail_message.schedule_with_attach(cr, uid, mail.email_from, to_email(mail.email_to), mail.subject, body,
+ model=mail.model, email_cc=to_email(mail.email_cc), email_bcc=to_email(mail.email_bcc), reply_to=mail.reply_to,
+ attachments=attachment, references=references, res_id=int(mail.res_id),
+ subtype=mail.subtype, headers=headers, context=context)
+ # in normal mode, we send the email immediately, as the user expects us to (delay should be sufficiently small)
+ mail_message.send(cr, uid, [msg_id], context)
+
+ return {'type': 'ir.actions.act_window_close'}
+
+ def render_template(self, cr, uid, template, model, res_id, context=None):
+ """Render the given template text, replace mako-like expressions ``${expr}``
+ with the result of evaluating these expressions with an evaluation context
+ containing:
+
+ * ``user``: browse_record of the current user
+ * ``object``: browse_record of the document record this mail is
+ related to
+ * ``context``: the context passed to the mail composition wizard
+
+ :param str template: the template text to render
+ :param str model: model name of the document record this mail is related to.
+ :param int res_id: id of the document record this mail is related to.
+ """
+ if context is None:
+ context = {}
+ def merge(match):
+ exp = str(match.group()[2:-1]).strip()
+ result = eval(exp,
+ {
+ 'user' : self.pool.get('res.users').browse(cr, uid, uid, context=context),
+ 'object' : self.pool.get(model).browse(cr, uid, res_id, context=context),
+ 'context': dict(context), # copy context to prevent side-effects of eval
+ })
+ if result in (None, False):
+ return ""
+ return tools.ustr(result)
+ return template and EXPRESSION_PATTERN.sub(merge, template)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml
new file mode 100644
index 00000000000..d559d679c8b
--- /dev/null
+++ b/addons/mail/wizard/mail_compose_message_view.xml
@@ -0,0 +1,60 @@
+
+
+
+
+ mail.compose.message.form
+ mail.compose.message
+ form
+
+
-
-
-
-
- mailgate.thread.tree
- mailgate.thread
- tree
-
-
-
-
-
-
-
-
-
-
-
-
- Mailgateway Threads
- mailgate.thread
- tree,form
- form
-
-
-
-
-
- tree
-
-
-
-
-
- form
-
-
-
-
-
-
- Emails
- mailgate.message
- tree,form
- form
- [('history', '=', True)]
-
-
-
-
-
- tree
-
-
-
-
-
- form
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/mail_gateway/res_partner_view.xml b/addons/mail_gateway/res_partner_view.xml
deleted file mode 100644
index b3d956f9fc1..00000000000
--- a/addons/mail_gateway/res_partner_view.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
- res.partner.crm.history.inherit1
- res.partner
- form
-
-
-
- False
-
-
-
-
- res.partner.emails.info.inherit
- res.partner
- form
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/mail_gateway/security/ir.model.access.csv b/addons/mail_gateway/security/ir.model.access.csv
deleted file mode 100644
index 09f13d57b31..00000000000
--- a/addons/mail_gateway/security/ir.model.access.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
-"mail_gateway_mailgate_message","mail_gateway.mailgate.message","model_mailgate_message","base.group_system",1,1,1,1
-"mail_gateway_mailgate_thread","mail_gateway.mailgate.thread","model_mailgate_thread","base.group_system",1,1,1,1
-"mail_gateway_message_internal_user","mail_gateway.mailgate.message.internal","model_mailgate_message","base.group_user",1,0,0,0
-"mail_gateway_mailgate_message_partner_manager","mail_gateway.mailgate.message.partner.manager","model_mailgate_message","base.group_partner_manager",1,1,1,1
diff --git a/addons/marketing/marketing_view.xml b/addons/marketing/marketing_view.xml
index b4539cfef33..9f70651e5ab 100644
--- a/addons/marketing/marketing_view.xml
+++ b/addons/marketing/marketing_view.xml
@@ -25,7 +25,7 @@
-
+
diff --git a/addons/marketing_campaign/marketing_campaign.py b/addons/marketing_campaign/marketing_campaign.py
index ae1ab2309e2..27c91fcc3fe 100644
--- a/addons/marketing_campaign/marketing_campaign.py
+++ b/addons/marketing_campaign/marketing_campaign.py
@@ -144,13 +144,6 @@ Normal - the campaign runs normally and automatically sends all emails and repor
if activity.signal and len(activity.from_ids) == 0:
has_signal_without_from = True
- if activity.type != 'email':
- continue
- if not activity.email_template_id.from_account:
- raise osv.except_osv(_("Error"), _("The campaign cannot be started: the email account is missing in email activity '%s'")%activity.name)
- if activity.email_template_id.from_account.state != 'approved':
- raise osv.except_osv(_("Error"), _("The campaign cannot be started: the email account is not approved in email activity '%s'")%activity.name)
-
if not has_start and not has_signal_without_from:
raise osv.except_osv(_("Error"), _("The campaign cannot be started: it doesn't have any starting activity (or any activity with a signal and no previous activity)"))
@@ -484,9 +477,9 @@ class marketing_campaign_activity(osv.osv):
return True
def _process_wi_email(self, cr, uid, activity, workitem, context=None):
- return self.pool.get('email.template').generate_mail(cr, uid,
+ return self.pool.get('email.template').send_mail(cr, uid,
activity.email_template_id.id,
- [workitem.res_id], context=context)
+ workitem.res_id, context=context)
def _process_wi_action(self, cr, uid, activity, workitem, context=None):
if context is None:
@@ -807,7 +800,7 @@ class marketing_campaign_workitem(osv.osv):
'type': 'ir.actions.act_window',
'target': 'new',
'nodestroy':True,
- 'context': "{'template_id':%d,'default_rel_model_ref':%d}"%
+ 'context': "{'template_id':%d,'default_res_id':%d}"%
(wi_obj.activity_id.email_template_id.id,
wi_obj.res_id)
}
@@ -831,7 +824,7 @@ marketing_campaign_workitem()
class email_template(osv.osv):
_inherit = "email.template"
_defaults = {
- 'object_name': lambda obj, cr, uid, context: context.get('object_id',False),
+ 'model_id': lambda obj, cr, uid, context: context.get('object_id',False),
}
# TODO: add constraint to prevent disabling / disapproving an email account used in a running campaign
diff --git a/addons/marketing_campaign/marketing_campaign_view.xml b/addons/marketing_campaign/marketing_campaign_view.xml
index 63bd16740ec..cd872e64635 100644
--- a/addons/marketing_campaign/marketing_campaign_view.xml
+++ b/addons/marketing_campaign/marketing_campaign_view.xml
@@ -95,10 +95,10 @@
-
-
+
+
-
+
@@ -200,12 +200,12 @@
-
-
-
-
+
+
+
+
-
+
@@ -267,7 +267,7 @@
+ context="{'default_model_id':object_id}" />
@@ -397,7 +397,7 @@
search
-
+
@@ -412,7 +412,7 @@
-
+
diff --git a/addons/marketing_campaign/report/campaign_analysis_view.xml b/addons/marketing_campaign/report/campaign_analysis_view.xml
index 745dd4047e5..653cc5c553d 100644
--- a/addons/marketing_campaign/report/campaign_analysis_view.xml
+++ b/addons/marketing_campaign/report/campaign_analysis_view.xml
@@ -59,7 +59,7 @@
-
+
diff --git a/addons/marketing_campaign/security/ir.model.access.csv b/addons/marketing_campaign/security/ir.model.access.csv
index c2b7b4f4c86..b0157b76c51 100644
--- a/addons/marketing_campaign/security/ir.model.access.csv
+++ b/addons/marketing_campaign/security/ir.model.access.csv
@@ -7,7 +7,7 @@
"access_marketing_campaign_analysis_campaignadmin","campaign.analysis","model_campaign_analysis","marketing.group_marketing_user",1,1,0,0
"access_marketing_campaign_workitem_all","marketing.campaign.workitem","model_marketing_campaign_workitem","base.group_user",1,0,0,0
"access_email_template_user","email.template","model_email_template","marketing.group_marketing_user",1,1,0,0
-"access_email_template_account_user","email_template.account.user","email_template.model_email_template_account","marketing.group_marketing_user",1,1,0,0
+"access_email_template_account_user","ir.mail_server","base.model_ir_mail_server","marketing.group_marketing_user",1,1,0,0
"access_marketing_campaign_system","marketing.campaign system","model_marketing_campaign","base.group_system",1,0,0,0
"access_marketing_campaign_segment_system","marketing.campaign.segment system","model_marketing_campaign_segment","base.group_system",1,0,0,0
"access_marketing_campaign_workitem_system","marketing.campaign.workitem system","model_marketing_campaign_workitem","base.group_system",1,0,0,0
@@ -18,4 +18,4 @@
"access_marketing_campaign_transition_campaign_manager","marketing.campaign.transition","model_marketing_campaign_transition","marketing.group_marketing_manager",1,1,1,1
"access_marketing_campaign_analysis_campaign_manager","campaign.analysis","model_campaign_analysis","marketing.group_marketing_manager",1,1,1,1
"access_email_template_manager","email.template","model_email_template","marketing.group_marketing_manager",1,1,1,1
-"access_email_template_account_manager","email_template.account.manager","email_template.model_email_template_account","marketing.group_marketing_manager",1,1,1,1
+"access_email_template_account_manager","ir.mail_server","base.model_ir_mail_server","marketing.group_marketing_manager",1,1,1,1
diff --git a/addons/membership/membership.py b/addons/membership/membership.py
index 6384a255ac3..09d9d34f973 100644
--- a/addons/membership/membership.py
+++ b/addons/membership/membership.py
@@ -379,7 +379,7 @@ class Partner(osv.osv):
return True
_constraints = [
- (_check_recursion, 'Error ! You can not create recursive associated members.', ['associate_member'])
+ (_check_recursion, 'Error ! You cannot create recursive associated members.', ['associate_member'])
]
def copy(self, cr, uid, id, default=None, context=None):
@@ -570,4 +570,4 @@ class account_invoice_line(osv.osv):
account_invoice_line()
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/membership/report/report_membership_view.xml b/addons/membership/report/report_membership_view.xml
index 463436439c6..0e6ddc630f6 100644
--- a/addons/membership/report/report_membership_view.xml
+++ b/addons/membership/report/report_membership_view.xml
@@ -35,7 +35,7 @@
search
-
+
-
+
-
+
diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml
index 9b2ea49e2b5..19e25438cad 100644
--- a/addons/mrp/mrp_view.xml
+++ b/addons/mrp/mrp_view.xml
@@ -79,13 +79,13 @@
search
-
+
-
+
@@ -183,7 +183,7 @@
search
-
+
@@ -191,7 +191,7 @@
-
+
@@ -289,7 +289,7 @@
-
+
@@ -420,14 +420,14 @@
search
-
+
-
+
@@ -796,7 +796,7 @@
search
-
+
@@ -817,7 +817,7 @@
-
+
diff --git a/addons/mrp/procurement.py b/addons/mrp/procurement.py
index 41a9d7993a2..4086a401f56 100644
--- a/addons/mrp/procurement.py
+++ b/addons/mrp/procurement.py
@@ -100,8 +100,9 @@ class procurement_order(osv.osv):
bom_result = production_obj.action_compute(cr, uid,
[produce_id], properties=[x.id for x in procurement.property_ids])
wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
- move_obj.write(cr, uid, [res_id],
- {'location_id': procurement.location_id.id})
+ if res_id:
+ move_obj.write(cr, uid, [res_id],
+ {'location_id': procurement.location_id.id})
return res
procurement_order()
diff --git a/addons/mrp/report/mrp_production_order_view.xml b/addons/mrp/report/mrp_production_order_view.xml
index f14825e1d16..dfcf41d9333 100644
--- a/addons/mrp/report/mrp_production_order_view.xml
+++ b/addons/mrp/report/mrp_production_order_view.xml
@@ -47,7 +47,7 @@
search
-
+
@@ -57,13 +57,12 @@
help="Production performed during current month"/>
-
+
diff --git a/addons/mrp/security/mrp_security.xml b/addons/mrp/security/mrp_security.xml
index 94f79769268..fb83f8c4a62 100644
--- a/addons/mrp/security/mrp_security.xml
+++ b/addons/mrp/security/mrp_security.xml
@@ -37,5 +37,19 @@
['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]
+
+ mrp_routing multi-company
+
+
+ ['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]
+
+
+
+ mrp_routing_workcenter multi-company
+
+
+ ['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]
+
+
diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py
index 0c1454edf98..a8dc4aecc89 100644
--- a/addons/mrp/stock.py
+++ b/addons/mrp/stock.py
@@ -46,6 +46,7 @@ class StockMove(osv.osv):
procurement_obj = self.pool.get('procurement.order')
product_obj = self.pool.get('product.product')
wf_service = netsvc.LocalService("workflow")
+ processed_ids = [move.id]
if move.product_id.supply_method == 'produce' and move.product_id.procure_method == 'make_to_order':
bis = bom_obj.search(cr, uid, [
('product_id','=',move.product_id.id),
@@ -55,11 +56,10 @@ class StockMove(osv.osv):
factor = move.product_qty
bom_point = bom_obj.browse(cr, uid, bis[0], context=context)
res = bom_obj._bom_explode(cr, uid, bom_point, factor, [])
- dest = move.product_id.product_tmpl_id.property_stock_production.id
state = 'confirmed'
if move.state == 'assigned':
state = 'assigned'
- for line in res[0]:
+ for line in res[0]:
valdef = {
'picking_id': move.picking_id.id,
'product_id': line['product_id'],
@@ -70,12 +70,12 @@ class StockMove(osv.osv):
'move_dest_id': move.id,
'state': state,
'name': line['name'],
- 'location_dest_id': dest,
'move_history_ids': [(6,0,[move.id])],
'move_history_ids2': [(6,0,[])],
'procurements': [],
}
mid = move_obj.copy(cr, uid, move.id, default=valdef)
+ processed_ids.append(mid)
prodobj = product_obj.browse(cr, uid, line['product_id'], context=context)
proc_id = procurement_obj.create(cr, uid, {
'name': (move.picking_id.origin or ''),
@@ -92,7 +92,7 @@ class StockMove(osv.osv):
})
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
move_obj.write(cr, uid, [move.id], {
- 'location_id': move.location_dest_id.id,
+ 'location_dest_id': move.location_id.id, # dummy move for the kit
'auto_validate': True,
'picking_id': False,
'state': 'confirmed'
@@ -100,7 +100,7 @@ class StockMove(osv.osv):
for m in procurement_obj.search(cr, uid, [('move_id','=',move.id)], context):
wf_service.trg_validate(uid, 'procurement.order', m, 'button_confirm', cr)
wf_service.trg_validate(uid, 'procurement.order', m, 'button_wait_done', cr)
- return True
+ return processed_ids
def action_consume(self, cr, uid, ids, product_qty, location_id=False, context=None):
""" Consumed product with specific quatity from specific source location.
@@ -112,6 +112,7 @@ class StockMove(osv.osv):
production_obj = self.pool.get('mrp.production')
wf_service = netsvc.LocalService("workflow")
for move in self.browse(cr, uid, ids):
+ move.action_confirm(context)
new_moves = super(StockMove, self).action_consume(cr, uid, [move.id], product_qty, location_id, context=context)
production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
for prod in production_obj.browse(cr, uid, production_ids, context=context):
@@ -155,16 +156,13 @@ class StockPicking(osv.osv):
#
# Explode picking by replacing phantom BoMs
#
- def action_explode(self, cr, uid, picks, *args):
- """ Explodes picking by replacing phantom BoMs
- @param picks: Picking ids.
- @param *args: Arguments
- @return: Picking ids.
- """
+ def action_explode(self, cr, uid, move_ids, *args):
+ """Explodes moves by expanding kit components"""
move_obj = self.pool.get('stock.move')
- for move in move_obj.browse(cr, uid, picks):
- move_obj._action_explode(cr, uid, move)
- return picks
+ todo = move_ids[:]
+ for move in move_obj.browse(cr, uid, move_ids):
+ todo.extend(move_obj._action_explode(cr, uid, move))
+ return list(set(todo))
StockPicking()
diff --git a/addons/mrp_operations/mrp_operations.py b/addons/mrp_operations/mrp_operations.py
index fe926d30ac1..4e665b3f9dc 100644
--- a/addons/mrp_operations/mrp_operations.py
+++ b/addons/mrp_operations/mrp_operations.py
@@ -131,7 +131,7 @@ class mrp_production_workcenter_line(osv.osv):
elif prod_obj.state =='in_production':
return
else:
- raise osv.except_osv(_('Error!'),_('Production Order Cannot start in [%s] state') % (prod_obj.state,))
+ raise osv.except_osv(_('Error!'),_('Manufacturing order cannot start in state "%s"!') % (prod_obj.state,))
else:
oper_ids = self.search(cr,uid,[('production_id','=',prod_obj.id)])
obj = self.browse(cr,uid,oper_ids)
@@ -458,30 +458,30 @@ class mrp_operations_operation(osv.osv):
code_lst.append(oper.code_id.start_stop)
if code.start_stop=='start':
if 'start' in code_lst:
- raise osv.except_osv(_('Sorry!'),_('Operation has already started !' 'You can either Pause /Finish/Cancel the operation'))
+ raise osv.except_osv(_('Sorry!'),_('Operation has already started !' 'Youcan either Pause/Finish/Cancel the operation'))
return False
if code.start_stop=='pause':
if code_lst[len(code_lst)-1]!='resume' and code_lst[len(code_lst)-1]!='start':
- raise osv.except_osv(_('Error!'),_('You cannot Pause the Operation other then Start/Resume state !'))
+ raise osv.except_osv(_('Error!'),_('In order to Pause the operation, it must be in the Start or Resume state!'))
return False
if code.start_stop=='resume':
if code_lst[len(code_lst)-1]!='pause':
- raise osv.except_osv(_('Error!'),_(' You cannot Resume the operation other then Pause state !'))
+ raise osv.except_osv(_('Error!'),_('In order to Resume the operation, it must be in the Pause state!'))
return False
if code.start_stop=='done':
if code_lst[len(code_lst)-1]!='start' and code_lst[len(code_lst)-1]!='resume':
- raise osv.except_osv(_('Sorry!'),_('You cannot finish the operation without Starting/Resuming it !'))
+ raise osv.except_osv(_('Sorry!'),_('In order to Finish the operation, it must be in the Start or Resume state!'))
return False
if 'cancel' in code_lst:
- raise osv.except_osv(_('Sorry!'),_('Operation is Already Cancelled !'))
+ raise osv.except_osv(_('Sorry!'),_('Operation is Already Cancelled!'))
return False
if code.start_stop=='cancel':
if not 'start' in code_lst :
- raise osv.except_osv(_('Error!'),_('There is no Operation to be cancelled !'))
+ raise osv.except_osv(_('Error!'),_('There is no Operation to be cancelled!'))
return False
if 'done' in code_lst:
- raise osv.except_osv(_('Error!'),_('Operation is already finished !'))
+ raise osv.except_osv(_('Error!'),_('Operation is already finished!'))
return False
return True
diff --git a/addons/mrp_operations/mrp_operations_view.xml b/addons/mrp_operations/mrp_operations_view.xml
index 92faddc66b0..a80a1a41a9c 100644
--- a/addons/mrp_operations/mrp_operations_view.xml
+++ b/addons/mrp_operations/mrp_operations_view.xml
@@ -17,7 +17,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
@@ -63,7 +63,7 @@
-
+
@@ -117,7 +117,7 @@
-
+
@@ -131,7 +131,7 @@
search
-
+
-
+
diff --git a/addons/mrp_operations/report/mrp_workorder_analysis_view.xml b/addons/mrp_operations/report/mrp_workorder_analysis_view.xml
index d25ec5132f6..a9cf0cbb718 100644
--- a/addons/mrp_operations/report/mrp_workorder_analysis_view.xml
+++ b/addons/mrp_operations/report/mrp_workorder_analysis_view.xml
@@ -46,7 +46,7 @@
search
-
+
@@ -56,13 +56,12 @@
help="Work orders made during current month"/>
-
+
diff --git a/addons/mrp_repair/mrp_repair_view.xml b/addons/mrp_repair/mrp_repair_view.xml
index 16cdfc86b3d..69782e00ec3 100644
--- a/addons/mrp_repair/mrp_repair_view.xml
+++ b/addons/mrp_repair/mrp_repair_view.xml
@@ -179,7 +179,7 @@
search
-
+
@@ -192,7 +192,7 @@
-
+
diff --git a/addons/mrp_subproduct/mrp_subproduct_view.xml b/addons/mrp_subproduct/mrp_subproduct_view.xml
index 9fc5955a18b..70b3ea8efaa 100644
--- a/addons/mrp_subproduct/mrp_subproduct_view.xml
+++ b/addons/mrp_subproduct/mrp_subproduct_view.xml
@@ -7,7 +7,7 @@
form
-
+
diff --git a/addons/multi_company/res_company_view.xml b/addons/multi_company/res_company_view.xml
index 42b60e13729..b246a1914d7 100644
--- a/addons/multi_company/res_company_view.xml
+++ b/addons/multi_company/res_company_view.xml
@@ -49,10 +49,10 @@
-
-
-
-
+
+
+
+
diff --git a/addons/outlook/__openerp__.py b/addons/outlook/__openerp__.py
index 53bd587583c..e376bcfa34a 100644
--- a/addons/outlook/__openerp__.py
+++ b/addons/outlook/__openerp__.py
@@ -25,17 +25,15 @@
'version' : '1.0',
'author' : 'OpenERP SA',
'website' : 'http://www.openerp.com/',
- 'depends' : ['base', 'mail_gateway'],
+ 'depends' : ['base', 'mail'],
'category' : 'Tools',
'description': '''
This module provides the Outlook Plug-in.
=========================================
-
Outlook plug-in allows you to select an object that you’d like to add
to your email and its attachments from MS Outlook. You can select a partner, a task,
a project, an analytical account, or any other object and archive selected
-mail in mailgate.messages with attachments.
-
+mail into email.messages with attachments.
''',
'init_xml' : [],
'demo_xml' : [],
diff --git a/addons/outlook/plugin/openerp-outlook-plugin/dialogs/dialog_map.py b/addons/outlook/plugin/openerp-outlook-plugin/dialogs/dialog_map.py
index a05220ad7e0..87138554e16 100644
--- a/addons/outlook/plugin/openerp-outlook-plugin/dialogs/dialog_map.py
+++ b/addons/outlook/plugin/openerp-outlook-plugin/dialogs/dialog_map.py
@@ -186,7 +186,6 @@ def check():
win32ui.MessageBox("No server running on host "+ server+" at port "+str(port), "OpenERP Connection", flag_excl)
return False
if str(NewConn.getitem('_login')) == 'False':
- win32ui.MessageBox("Please login to the database first", "OpenERP Connection", flag_excl)
return False
return True
diff --git a/addons/outlook/plugin/openerp-outlook-plugin/dialogs/resources/dialogs.py b/addons/outlook/plugin/openerp-outlook-plugin/dialogs/resources/dialogs.py
index 247b9c7a533..aa417b2cd59 100644
--- a/addons/outlook/plugin/openerp-outlook-plugin/dialogs/resources/dialogs.py
+++ b/addons/outlook/plugin/openerp-outlook-plugin/dialogs/resources/dialogs.py
@@ -1,7 +1,7 @@
-#C:\workspace\openerp-outlook-plugin\dialogs\resources\dialogs.py
-#This is a generated file. Please edit C:\workspace\openerp-outlook-plugin\dialogs\resources\dialogs.rc instead.
+#C:\Program Files\Openerp Outlook Addin\dialogs\resources\dialogs.py
+#This is a generated file. Please edit C:\Program Files\Openerp Outlook Addin\dialogs\resources\dialogs.rc instead.
_rc_size_=16350
-_rc_mtime_=1296563638
+_rc_mtime_=1296608638
class FakeParser:
dialogs = {'IDD_OPEN_DOCUEMNT_DIALOG': [['Open Document', (0, 0, 200, 65), -1865940928, 1024, (8, 'Tahoma')], [130, 'Link to Document : (If this link does not opens directly in web browser then copy an the link and paste in web browser.) ', -1, (5, 3, 190, 25), 1342177280], [129, '', 2086, (5, 31, 190, 12), 1350631552], [128, 'Ok', 2, (150, 45, 45, 14), 1342242816]], 'IDD_VIEW_PARTNER_DIALOG': [['Open Contact', (0, 0, 350, 215), -1865940928, 1024, (8, 'Tahoma')], [130, 'Email ID : ', -1, (32, 17, 40, 12), 1342177280], [129, '', 2051, (70, 15, 200, 12), 1350631552], [128, 'Search Contact ', 2052, (280, 15, 60, 14), 1342242816], [130, 'Partner Name : ', -1, (13, 42, 50, 17), 1342177280], [129, '', 2064, (70, 42, 150, 12), 1350568064], [128, 'Search Partner', 2070, (225, 41, 60, 14), 1342242816], [128, 'New Partner', 2093, (290, 41, 50, 14), 1342242816], [128, 'Postal Address ', 2024, (10, 65, 175, 125), 1342177287], [130, 'Contact Name : ', -1, (18, 83, 50, 17), 1342177280], [129, '', 2046, (76, 82, 100, 12), 1350631552], [130, 'Street : ', -1, (18, 97, 50, 17), 1342177280], [129, '', 2053, (76, 96, 100, 12), 1350631552], [130, 'Street2 : ', -1, (18, 112, 50, 17), 1342177280], [129, '', 2054, (76, 111, 100, 12), 1350631552], [130, 'Zip : ', -1, (18, 126, 50, 14), 1342177280], [129, '', 40010, (76, 125, 100, 12), 1350631552], [130, 'City : ', -1, (18, 140, 50, 17), 1342177280], [129, '', 40011, (76, 139, 100, 12), 1350631552], [130, 'Fed. State : ', -1, (18, 154, 50, 17), 1342177280], [129, '', 2062, (76, 153, 54, 12), 1350568064], [128, 'Search', 2104, (135, 153, 40, 12), 1342242816], [130, 'Country :', -1, (18, 169, 35, 17), 1342177280], [129, '', 2063, (76, 168, 54, 12), 1350568064], [128, 'Search', 2105, (135, 168, 40, 12), 1342242816], [128, 'Communication ', 2024, (188, 65, 152, 125), 1342177287], [130, 'Phone : ', -1, (194, 83, 30, 17), 1342177280], [129, '', 2048, (224, 82, 100, 12), 1350631552], [130, 'Mobile : ', -1, (194, 97, 30, 17), 1342177280], [129, '', 2050, (224, 96, 100, 12), 1350631552], [130, 'Email : ', -1, (194, 111, 30, 17), 1342177280], [129, '', 2058, (224, 110, 100, 12), 1350568064], [130, 'Fax : ', -1, (194, 125, 30, 17), 1342177280], [129, '', 2047, (224, 124, 100, 12), 1350631552], [128, 'Create a New Contact', 2071, (124, 195, 82, 14), 1342242816], [128, 'Save', 2059, (213, 195, 60, 14), 1342242816], [128, 'Cancel', 2, (281, 195, 60, 14), 1342242816]], 'IDD_OPEN_PARTNER_DIALOG': [['Open Partner', (0, 0, 200, 65), -1865940928, 1024, (8, 'Tahoma')], [130, 'Link to Partner : (If this link does not opens directly in web browser then copy an the link and paste in web browser.) ', -1, (5, 3, 190, 25), 1342177280], [129, '', 2085, (5, 31, 190, 12), 1350631552], [128, 'Ok', 2, (150, 45, 45, 13), 1342242816]], 'IDD_SELECT_STATE': [['Search Fed. State', (0, 0, 220, 250), -1865940928, 1024, (8, 'Tahoma')], [130, 'Enter Name : ', -1, (8, 12, 80, 17), 1342177280], [129, '', 2100, (55, 10, 100, 12), 1350631552], [128, 'Search', 40012, (160, 10, 50, 14), 1342242816], ['SysListView32', 'List1', 2101, (8, 40, 200, 185), 1353711657], [128, 'Select', 2102, (170, 230, 40, 14), 1342242816]], 'IDD_GENERAL': [['Connection Parameters', (0, 0, 430, 210), 1355284544, None, (8, 'Tahoma')], [128, 'Connection Parameters', 2024, (10, 10, 250, 130), 1342177287], [130, 'Server : ', -1, (30, 30, 100, 17), 1342177280], [129, '', 2003, (85, 30, 120, 12), 1350633472], [128, 'Change', 2021, (210, 30, 30, 13), 1342242816], [130, 'Database : ', -1, (30, 50, 100, 17), 1342177280], [133, '', 2004, (85, 50, 154, 40), 1344339971], [130, 'Username : ', -1, (30, 70, 100, 17), 1342177280], [129, '', 2005, (85, 70, 154, 12), 1350631552], [130, 'Password : ', -1, (30, 90, 100, 17), 1342177280], [129, '', 2006, (85, 90, 154, 12), 1350631584], [128, 'Connect', 2007, (179, 110, 60, 13), 1342242816], [128, 'WebServer Parameters', 2024, (270, 10, 150, 130), 1342177287], [130, 'Server : ', -1, (280, 30, 50, 15), 1342177280], [129, '', 2080, (310, 29, 90, 12), 1350568064], [128, 'Change', 2082, (295, 50, 50, 12), 1342242816], [128, 'Connect', 2083, (350, 50, 50, 12), 1342242816]], 'IDD_OBJECT_SETTINGS': [['Documents Setting', (0, 0, 430, 210), 1355284672, 1024, (8, 'Tahoma')], [128, 'Document Attributes', 2024, (2, 2, 343, 32), 1342177287], [130, 'Title:', -1, (5, 16, 30, 17), 1342177280], [129, '', 2013, (22, 14, 57, 12), 1350631552], [130, 'Document Name:', -1, (83, 16, 60, 17), 1342177280], [129, '', 2014, (138, 14, 57, 12), 1350631552], [130, 'Image:', -1, (200, 16, 22, 17), 1342177280], [129, '', 2015, (223, 14, 57, 12), 1350631552], [128, 'Load Image', 2010, (289, 13, 50, 15), 1342242816], [128, 'Add', 2011, (350, 13, 38, 15), 1342242816], [128, 'Delete', 2012, (392, 13, 38, 15), 1342242816], ['SysListView32', 'List1', 2016, (8, 40, 535, 160), 1353711657]], 'IDD_NEW_PARTNER_DIALOG': [['Create a New Partner', (0, 0, 140, 40), -1865940928, 1024, (8, 'Tahoma')], [130, 'Name : ', -1, (5, 3, 100, 17), 1342177280], [129, '', 2036, (40, 3, 94, 12), 1350631552], [128, 'Cancel', 2, (90, 22, 45, 14), 1342242816], [128, 'Save', 2035, (40, 22, 45, 14), 1342242817]], 'IDD_MANAGER': [['OpenERP Configuration', (0, 0, 460, 260), -1865940800, None, (8, 'Tahoma')], [128, 'Close', 2008, (400, 239, 50, 14), 1342177281], ['SysTabControl32', '', 1068, (8, 7, 440, 228), 1342177280], ['SysListView32', '', 2016, (0, 0, 0, 0), 1353711657]], 'IDD_WEB_SERVER_PORT_DIALOG': [['OpenERP Connection', (0, 0, 160, 80), -1865940928, 1024, (8, 'Tahoma')], [130, 'Server : ', -1, (5, 3, 150, 17), 1342177280], [129, '', 2089, (40, 3, 100, 12), 1350631552], [130, 'Port : ', -1, (5, 18, 100, 17), 1342177280], [129, '', 2090, (40, 18, 100, 12), 1350631552], [128, 'Close', 2, (110, 50, 45, 14), 1342242816], [128, 'OK', 2091, (50, 50, 50, 14), 1342242817], [128, 'SSL (https)', 2111, (20, 35, 100, 14), 1342242819]], 'IDD_ABOUT': [['About', (0, 0, 430, 210), 1355284672, 1024, (8, 'Tahoma')], [128, 'About Plugin', -1, (7, 3, 422, 200), 1342177287], [130, '1062', 1062, (60, 30, 20, 20), 1342179342], [130, '', 2028, (80, 90, 300, 100), 1342177280]], 'IDD_NEW_CONTACT_DIALOG': [['Create a New Contact', (0, 0, 350, 180), -1865940928, 1024, (8, 'Tahoma')], [130, 'Select Partner : ', -1, (13, 20, 50, 17), 1342177280], [129, '', 2079, (70, 18, 150, 12), 1350568064], [128, 'Search Partner', 2033, (225, 17, 60, 14), 1342242816], [128, 'New Partner', 2092, (290, 17, 50, 14), 1342242816], [128, 'Postal Address ', 2024, (10, 35, 175, 123), 1342177287], [130, 'Contact Name : ', -1, (18, 53, 100, 17), 1342177280], [129, '', 40005, (76, 52, 100, 12), 1350631552], [130, 'Street : ', -1, (18, 67, 40, 17), 1342177280], [129, '', 2062, (76, 66, 100, 12), 1350631552], [130, 'Street2 : ', -1, (18, 81, 40, 17), 1342177280], [129, '', 2063, (76, 80, 100, 12), 1350631552], [130, 'Zip : ', -1, (18, 94, 50, 17), 1342177280], [129, '', 2068, (76, 93, 100, 12), 1350631552], [130, 'City : ', -1, (18, 106, 50, 17), 1342177280], [129, '', 2067, (76, 107, 100, 12), 1350631552], [130, 'Fed. State : ', -1, (18, 134, 50, 15), 1342177280], [129, '', 2106, (76, 135, 54, 12), 1350568064], [128, 'Search', 2107, (135, 136, 40, 12), 1342242816], [130, 'Country :', -1, (18, 121, 35, 15), 1342177280], [129, '', 2108, (76, 122, 54, 12), 1350568064], [128, 'Search', 2109, (135, 121, 40, 12), 1342242816], [128, 'Communication ', 2024, (188, 35, 150, 123), 1342177287], [130, 'Office : ', -1, (194, 53, 30, 17), 1342177280], [129, '', 40006, (224, 52, 100, 12), 1350631552], [130, 'Mobile : ', -1, (194, 67, 30, 17), 1342177280], [129, '', 40007, (224, 66, 100, 12), 1350631552], [130, 'Fax : ', -1, (194, 81, 30, 17), 1342177280], [129, '', 2066, (224, 80, 100, 12), 1350631552], [130, 'Email : ', -1, (194, 95, 30, 17), 1342177280], [129, '', 40008, (224, 94, 100, 12), 1350631552], [128, 'Cancel', 2, (290, 162, 45, 13), 1342242816], [128, 'Save', 40009, (240, 162, 45, 13), 1342242816]], 'IDD_SERVER_PORT_DIALOG': [['OpenERP Connection', (0, 0, 160, 90), -1865940928, 1024, (8, 'Tahoma')], [130, 'Server : ', -1, (5, 3, 150, 17), 1342177280], [129, '', 2001, (45, 3, 100, 12), 1350631552], [130, ' Port : ', -1, (5, 18, 100, 17), 1342177280], [129, '', 2002, (45, 18, 100, 12), 1350631552], [128, '', 2024, (5, 35, 150, 5), 1342177287], [130, 'Protocol Connection :', -1, (5, 45, 80, 17), 1342177280], [133, '', 2110, (75, 43, 75, 50), 1344339971], [128, 'Close', 2, (60, 70, 45, 14), 1342242816], [128, 'OK', 1, (110, 70, 45, 14), 1342242817]], 'IDD_SELECT_PARTNER': [['Search Partner', (0, 0, 220, 250), -1865940928, 1024, (8, 'Tahoma')], [130, 'Enter Name : ', -1, (8, 12, 80, 17), 1342177280], [129, '', 2076, (55, 10, 100, 12), 1350631552], [128, 'Search', 2077, (160, 10, 50, 14), 1342242816], ['SysListView32', 'List1', 2072, (8, 40, 200, 185), 1353711657], [128, 'Create New Partner', 2078, (10, 230, 100, 14), 1342242816], [128, 'Select', 2075, (170, 230, 40, 14), 1342242816]], 'IDD_SYNC': [['Push to OpenERP', (0, 0, 470, 320), -1865940928, 1024, (8, 'Tahoma')], [128, 'Link to an Existing Documents ', 2024, (8, 5, 250, 290), 1342242823], [130, 'Search : ', -1, (15, 17, 40, 12), 1342177280], [129, '', 40003, (60, 15, 120, 12), 1350631552], [128, 'Search', 40004, (187, 15, 40, 14), 1342242816], [130, 'Documents : ', -1, (15, 140, 100, 14), 1342177280], ['SysListView32', 'List1', 2026, (15, 150, 234, 110), 1350631433], [128, 'Push', 2019, (160, 270, 85, 14), 1342242816], [128, ' Create a New Document ', 2024, (263, 5, 202, 100), 1342242823], [130, 'Type of Document : ', -1, (266, 25, 100, 12), 1342177280], [133, '', 2025, (332, 24, 75, 45), 1344339971], [128, 'Create', 2020, (412, 23, 50, 14), 1342242816], [128, ' Create a New Contact ', 2024, (263, 110, 202, 185), 1342242823], [130, 'Create a New Contact : ', -1, (280, 140, 100, 12), 1342177280], [128, 'Create Contact', 2018, (360, 138, 60, 14), 1342242816], [128, 'Close', 2, (385, 300, 60, 14), 1342242816]], 'IDD_SELECT_COUNTRY': [['Search Country', (0, 0, 220, 250), -1865940928, 1024, (8, 'Tahoma')], [130, 'Enter Name : ', -1, (8, 12, 80, 17), 1342177280], [129, '', 2095, (55, 10, 100, 12), 1350631552], [128, 'Search', 2098, (160, 10, 50, 14), 1342242816], ['SysListView32', 'List1', 2096, (8, 40, 200, 185), 1353711657], [128, 'Select', 2097, (170, 230, 40, 14), 1342242816]]}
ids = {'IDC_BUT_DEL_OBJECT': 2012, 'IDD_OPEN_PARTNER_DIALOG': 2084, 'IDD_SELECT_STATE': 2099, 'IDC_DELAY1_SLIDER': 1056, 'IDC_PROGRESS': 1000, 'IDD_MANAGER': 101, 'IDC_ABOUT': 2028, 'IDD_DIAGNOSTIC': 113, 'IDET_PARTNER_COUNTRY': 2063, 'IDD_TRAINING': 102, 'ID_CONTACT_EMAIL_TEXT': 40008, 'IDC_DELAY2_TEXT': 1059, 'IDC_DELAY1_TEXT': 1057, 'IDD_WIZARD': 114, 'IDC_CHKBX': 2023, 'IDC_STATIC_HAM': 1002, 'IDC_PROGRESS_TEXT': 1001, 'IDR_XMLS_PROTOCOL': 2042, 'IDD_GENERAL': 108, 'IDD_ABOUT': 2027, 'IDD_SYNC': 40002, 'IDC_TAB': 1068, 'IDC_FOLDER_UNSURE': 1033, '_APS_NEXT_SYMED_VALUE': 101, 'IDC_VERBOSE_LOG': 1061, 'IDC_EDIT1': 1094, 'IDC_BROWSE': 1037, 'ID_DB_DROPDOWNLIST': 2004, 'IDC_BACK_BTN': 1069, 'ID_CONTACT_NAME_TEXT': 40005, 'IDPB_PARTNER_SEARCH': 2077, 'IDD_WIZARD_FINISHED_UNCONFIGURED': 119, 'IDC_ACTION_CERTAIN': 1025, 'IDC_BUT_ACT_ALL': 1019, 'IDD_FILTER_NOW': 104, 'IDET_WED_SERVER': 2080, 'ID_WEB_OK': 2091, 'ID_PROTOCOL_GRP': 2040, 'IDC_HEADER': 2017, 'IDC_MARK_SPAM_AS_READ': 1047, 'IDPB_NEW_PARTNER_BUTTON': 2092, 'ID_PARTNER_NAME_TEXT': 2036, 'IDC_RECOVER_RS': 1075, 'IDC_NAME_LIST1': 2037, 'ID_CONTACT_OFFICE_TEXT': 40006, 'IDC_STATIC': -1, 'IDC_PAGE_PLACEHOLDER': 1078, 'IDC_BROWSE_WATCH': 1039, 'IDET_ZIP': 40010, 'IDET_PARTNER_CITY': 40011, 'IDET_PARTNER_LINK_TEXT': 2085, 'IDC_FOLDER_HAM': 1083, 'IDC_LIST_COUNTRY': 2096, 'IDD_WIZARD_FOLDERS_REST': 117, 'IDC_SHOW_DATA_FOLDER': 1071, 'IDC_BUT_ACT_SCORE': 1018, 'IDET_STATE_SEARCH_NAME': 2100, 'IDET_WEB_SERVER': 2089, '_APS_NEXT_RESOURCE_VALUE': 128, 'ID_SET_WEB_CONNECTION': 2082, 'ID_DROPDOWNLIST_PROTOCOL': 2110, 'IDC_LIST_STATE': 2101, 'IDC_CONTACT_LIST': 2029, 'IDC_SLIDER_CERTAIN': 1023, 'IDET_PARTNER_STREET': 2053, 'IDC_BUT_UNREAD': 1020, 'ID_PARTNER_DROPDOWNLIST': 2032, 'ID_COUNTRY_DROPLIST': 2065, 'IDC_BUT_ABOUT': 1017, 'IDC_BUT_RESCORE': 1008, 'IDC_BUT_SEARCHSUB': 1041, 'IDC_BUT_TRAIN_FROM_SPAM_FOLDER': 1010, 'IDET_PARTNER_MOBILENO': 2050, 'ID_CONTACT_MOBILE_TEXT': 40007, 'IDD_VIEW_PARTNER_DIALOG': 2044, 'IDD_WIZARD_FOLDERS_TRAIN': 120, 'IDC_BUT_FILTER_ENABLE': 1013, 'IDPB_WEB_CONNECTION': 2083, 'IDC_ABOUT_BTN': 1072, 'IDD_WIZARD_FINISHED_TRAINED': 122, 'ID_SERVER': 2001, 'IDD_SELECT_PARTNER': 2073, 'IDET_NC_PARTNER_STATE': 2106, 'IDD_FOLDER_SELECTOR': 105, 'IDC_BUT_SET_SERVER_PORT': 2021, 'ID_DONE': 2008, 'IDC_LIST_FOLDERS': 1040, 'IDC_IMAGE_PATH': 2015, 'IDB_SBWIZLOGO': 125, 'IDB_OPENERPLOGO': 1062, 'ID_ZIP_TEXT': 2068, 'IDEB_OPENDOC_LINK_TEXT': 2086, 'IDD_NEW_PARTNER_DIALOG': 2034, 'ID_PARTNER_DROPLIST': 2069, 'IDC_BUT_VIEW_LOG': 1093, 'IDC_STATUS2': 1044, 'IDC_STATUS1': 1043, 'IDCANCEL': 2, 'IDC_BROWSE_HAM': 1004, 'ID_BUT_TESTCONNECTION': 2007, 'IDR_NETRPC_PROTOCOL': 2043, 'IDC_BROWSE_SPAM': 1005, 'IDCB_WEB_SECURE': 2111, 'IDD_OPEN_DOCUEMNT_DIALOG': 2087, 'IDD_WIZARD_FINISHED_UNTRAINED': 116, 'IDC_MARK_UNSURE_AS_READ': 1051, 'IDPB_SEARCH_COUNTRY1': 2105, 'ID_PARTNER_CITY_TEXT': 2067, 'IDC_BUT_WIZARD': 1070, 'IDC_VERSION': 1009, 'ID_NEW_PARTNER_BUTTON': 2033, 'IDC_FOLDER_NAMES': 1036, 'ID_ATT_METHOD_DROPDOWNLIST': 2025, 'IDC_BUT_TIMER_ENABLED': 1091, 'IDPB_WRITE_CHANGES': 2059, 'IDC_SLIDER_UNSURE': 1029, 'IDC_BUT_NEW': 1046, 'IDC_FOLDER_WATCH': 1038, 'IDPB_CREATE_NEW_PARTNER': 2078, 'IDC_BUT_UNTRAINED': 1088, 'IDC_STATIC_SPAM': 1003, 'IDD_NEW_CONTACT_DIALOG': 2031, 'IDC_EDIT_UNSURE': 1030, 'IDC_BUT_CLEARALL': 1042, 'IDC_BUT_UNSEEN': 1021, 'IDC_OBJECT_NAME': 2014, 'IDD_WIZARD_FOLDERS_WATCH': 118, 'IDPB_SEARCH_STATE': 2103, 'IDET_COUNTRY_SEARCH_NAME': 2095, 'IDC_BUT_SAVE_OBJECT': 2011, 'ID_FED_STATE_DROPLIST': 2064, 'ID_ALL_COUNTRY_DROPDOWNLIST': 2061, 'IDC_EDIT_CERTAIN': 1024, 'IDC_BUT_FILTER_DEFINE': 1016, 'ID_NEW_PART_BUTTON': 2070, 'ID_FAX_TEXT': 2066, 'IDD_WIZARD_TRAINING_IS_IMPORTANT': 123, 'ID_ALL_STATE_DROPDOWNLIST': 2060, 'IDPB_SEARCH_STATE1': 2104, 'IDC_INBOX_TIMER_ONLY': 1060, 'IDPB_SEARCH_PARTNER': 2052, 'ID_USERNAME': 2005, '_APS_NEXT_CONTROL_VALUE': 1096, 'IDC_WIZ_GRAPHIC': 1092, 'IDD_OBJECT_SETTINGS': 2009, 'IDD_FILTER_UNSURE': 111, 'IDC_DEL_SPAM_RS': 1074, 'IDD_SELECT_COUNTRY': 2094, 'ID_SERVER_PORT': 2003, 'IDR_XML_PROTOCOL': 2041, 'IDET_PARTNER_OFFICENO': 2048, 'IDB_FOLDERS': 127, 'IDC_BUT_PREPARATION': 1081, 'ID_STREET2_TEXT': 2063, 'IDC_DELAY2_SLIDER': 1058, 'IDET_PARTNER': 2064, 'IDC_SAVE_SPAM_SCORE': 1048, 'IDC_OBJECT_TITLE': 2013, 'IDC_FOLDER_CERTAIN': 1027, 'IDET_WEB_PORT': 2090, 'IDC_BROWSE_UNSURE': 1034, 'IDC_STATISTICS': 1095, 'IDPB_STATE_SEARCH': 40012, 'ID_MAKE_ATTACHMENT': 2019, 'IDC_BUT_LOAD_IMAGE': 2010, 'IDC_NAME_LIST': 2026, 'IDC_BUT_TRAIN_TO_SPAM_FOLDER': 1011, 'IDET_PARTNER_SEARCH_NAME': 2076, 'IDC_BUT_RESET': 1073, 'ID_SEARCH': 40004, 'IDET_PARTNER_CONTACT_NAME': 2046, 'IDPB_SELECT_STATE': 2102, 'IDC_ACTION_UNSURE': 1031, 'IDD_WIZARD_TRAIN': 121, 'IDC_STATIC_GROUP': 2024, 'IDPB_NEWPARTNER_BUTTON': 2071, 'ID_PARTNER_TEXT': 2079, 'IDD_WIZARD_FINISHED_TRAIN_LATER': 124, 'IDET_PARTNER_NAME': 2045, 'IDPB_NEW_PART_BUTTON': 2093, 'IDC_BUT_REBUILD': 1007, 'IDET_PARTNER_STREET2': 2054, 'IDPB_SEARCH_COUNTRY': 2098, 'ID_SAVE_PARTNER_BUTTON': 2035, 'IDPB_SELECT_PARTNER': 2075, 'ID_STREET_TEXT': 2062, '_APS_NEXT_COMMAND_VALUE': 40001, 'IDET_SEARCH_PARTNER': 2051, 'IDC_LIST_PARTNER': 2072, 'IDD_WEB_SERVER_PORT_DIALOG': 2088, 'ID_PORT': 2002, 'IDD_SERVER_PORT_DIALOG': 2022, 'IDET_PARTNER_STATE': 2062, 'IDPB_SELECT_COUNTRY': 2097, 'ID_SEARCH_TEXT': 40003, 'IDET_NC_PARTNER_COUNTRY': 2108, 'ID_CREATE_CONTACT': 2018, 'IDC_FORWARD_BTN': 1077, 'IDC_TRAINING_STATUS': 1035, 'IDD_WIZARD_WELCOME': 115, 'IDET_PARTNER_FAX': 2058, 'IDC_BUT_TRAIN': 1089, 'IDC_LIST': 2016, 'ID_CREATE_CASE': 2020, 'IDET_PARTNER_EMAIL': 2047, 'IDC_START': 1006, 'IDD_FILTER': 103, 'IDC_RELOAD': 2038, 'ID_PASSWORD': 2006, 'ID_NAME_TEXT': 2030, 'IDC_FILTER_STATUS': 1014, 'IDPB_NC_SEARCH_STATE1': 2107, 'ID_CONTACT_SAVE_BUTTON': 40009, 'IDOK': 1, 'IDC_BROWSE_CERTAIN': 1028, 'IDC_BUT_SHOW_DIAGNOSTICS': 1080, 'IDC_BUT_TRAIN_NOW': 1012, 'IDPB_NC_SEARCH_COUNTRY1': 2109}
diff --git a/addons/outlook/plugin/openerp-outlook-plugin/tiny_xmlrpc.py b/addons/outlook/plugin/openerp-outlook-plugin/tiny_xmlrpc.py
index 7ecb2aea81f..41346fe7cf9 100644
--- a/addons/outlook/plugin/openerp-outlook-plugin/tiny_xmlrpc.py
+++ b/addons/outlook/plugin/openerp-outlook-plugin/tiny_xmlrpc.py
@@ -20,6 +20,8 @@
##############################################################################
import xmlrpclib
+import binascii
+import base64
import sys
import socket
import os
@@ -146,6 +148,7 @@ class XMLRpcConn(object):
flag = False
new_msg = ext_msg =""
message_id = referances = None
+ context = {}
try:
session = win32com.client.Dispatch("MAPI.session")
session.Logon('Outlook')
@@ -177,15 +180,15 @@ class XMLRpcConn(object):
for rec in recs: #[('res.partner', 3, 'Agrolait')]
model = rec[0]
res_id = rec[1]
- #Check if mailgate installed
- object_id = execute ( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[('model','=','mailgate.message')])
+ #Check if mail installed
+ object_id = execute ( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[('model','=','mail.message')])
if not object_id:
- win32ui.MessageBox("Mailgate is not installed on your configured database '%s' !!\n\nPlease install it to archive the mail."%(self._dbname),"Mailgate not installed",win32con.MB_ICONERROR)
+ win32ui.MessageBox("Mail is not installed on your configured database '%s' !!\n\nPlease install it to archive the mail."%(self._dbname),"Mail not installed",win32con.MB_ICONERROR)
return
object_ids = execute ( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[('model','=',model)])
object_name = execute( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','read',object_ids,['name'])[0]['name']
#Reading the Object ir.model Name
- ext_ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'mailgate.message','search',[('message_id','=',message_id),('model','=',model),('res_id','=',res_id)])
+ ext_ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'mail.message','search',[('message_id','=',message_id),('model','=',model),('res_id','=',res_id)])
if ext_ids:
name = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,model,'read',res_id,['name'])['name']
ext_msg += """This mail is already archived to {0} '{1}'.\n""".format(object_name,name)
@@ -201,14 +204,30 @@ class XMLRpcConn(object):
'message-id':message_id,
'references':ustr(referances),
}
- obj_list= ['crm.lead','project.issue','hr.applicant','res.partner']
- if rec[0] not in obj_list:
- self.CreateEmailAttachment(rec,mail)
result = {}
+ context['thread_model'] = model
if attachments:
result = self.MakeAttachment([rec], mail)
- attachment_ids = result.get(model, {}).get(res_id, [])
- execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'email.server.tools','history',model, res_id, msg, attachment_ids)
+ execute(conn, 'execute', self. _dbname, int(self._uid), self._pwd,
+ 'mail.thread','message_append',[res_id],
+ msg.get('subject', False),
+ msg.get('body', False),
+ msg.get('to', False),
+ msg.get('from', False),
+ msg.get('cc', False),
+ False, # BCC
+ False, #reply-to
+ msg.get('date', False),
+ msg.get('message-id'),\
+ msg.get('references', False),
+ result, #attachments
+ #FIXME: properly handle plaintext/html body variants?
+ False, #body_html
+ False, #subtype
+ False, #headers
+ False, #original
+ context)
+
new_msg += """- {0} : {1}\n""".format(object_name,str(rec[2]))
flag = True
@@ -298,7 +317,7 @@ class XMLRpcConn(object):
endCut = message_id.find(">")
message_id = message_id[startCut:endCut+1]
email.replace_header('Message-Id',message_id)
- id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'email.server.tools','process_email',section, str(email))
+ id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'mail.thread','message_process',section, str(email))
if id > 0:
flag = True
return flag
@@ -310,39 +329,28 @@ class XMLRpcConn(object):
return flag
def MakeAttachment(self, recs, mail):
- attachments = mail.Attachments
- result = {}
- conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
- att_folder_path = os.path.abspath(os.path.dirname("%temp%\\"))
- if not os.path.exists(att_folder_path):
+ attachments = mail.Attachments
+ attachment = {}
+ conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
+ att_folder_path = os.path.abspath(os.path.dirname("%temp%\\"))
+ if not os.path.exists(att_folder_path):
os.makedirs(att_folder_path)
for rec in recs: #[('res.partner', 3, 'Agrolait')]
- obj = rec[0]
- obj_id = rec[1]
- res={}
- res['res_model'] = obj
- attachment_ids = []
- if obj not in result:
- result[obj] = {}
- for i in xrange(1, attachments.Count+1):
- fn = ustr(attachments[i].FileName)
- if len(fn) > 64:
- l = 64 - len(fn)
- f = fn.split('.')
- fn = f[0][0:l] + '.' + f[-1]
- att_path = os.path.join(att_folder_path,fn)
- attachments[i].SaveAsFile(att_path)
- f=open(att_path,"rb")
- content = "".join(f.readlines()).encode('base64')
- f.close()
- res['name'] = ustr(attachments[i].DisplayName)
- res['datas_fname'] = ustr(fn)
- res['datas'] = content
- res['res_id'] = obj_id
- id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.attachment','create',res)
- attachment_ids.append(id)
- result[obj].update({obj_id: attachment_ids})
- return result
+ obj = rec[0]
+ obj_id = rec[1]
+ for i in xrange(1, attachments.Count+1):
+ fn = ustr(attachments[i].FileName)
+ if len(fn) > 64:
+ l = 64 - len(fn)
+ f = fn.split('.')
+ fn = f[0][0:l] + '.' + f[-1]
+ att_path = os.path.join(att_folder_path,fn)
+ attachments[i].SaveAsFile(att_path)
+ f=open(att_path,"rb")
+ content = "".join(f.readlines())
+ f.close()
+ attachment[fn] = content
+ return attachment
def CreateContact(self, res=None):
res=eval(str(res))
@@ -456,17 +464,17 @@ class XMLRpcConn(object):
import win32ui
conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
res_vals = []
- mail_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message', 'search', [('message_id','=',message_id)])
+ mail_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mail.message', 'search', [('message_id','=',message_id)])
ref_mail_id = None
if not mail_id:
- ref_mail_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message', 'search', [('references','=',message_id)])
+ ref_mail_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mail.message', 'search', [('references','like','%'+message_id+'%')])
if ref_mail_id:
- address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message','read',ref_mail_id[0],['model','res_id'])
+ address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mail.message','read',ref_mail_id[0],['model','res_id'])
for key, vals in address.items():
res_vals.append([key,vals])
return res_vals
return None
- address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message','read',mail_id[0],['model','res_id'])
+ address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mail.message','read',mail_id[0],['model','res_id'])
for key, vals in address.items():
res_vals.append([key,vals])
return res_vals
diff --git a/addons/point_of_sale/account_statement_view.xml b/addons/point_of_sale/account_statement_view.xml
index 45f89c5062c..d6bfa4eda66 100644
--- a/addons/point_of_sale/account_statement_view.xml
+++ b/addons/point_of_sale/account_statement_view.xml
@@ -39,7 +39,7 @@
search
-
+
@@ -64,7 +64,7 @@
search
-
+
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index 31c580ba506..04610463203 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -114,10 +114,18 @@ class pos_order(osv.osv):
if date_p:
res[order.id] = date_p
return res
- cr.execute(" SELECT MAX(l.date) "
- " FROM account_move_line l, account_move m, account_invoice i, account_move_reconcile r, pos_order o "
- " WHERE i.move_id = m.id AND l.move_id = m.id AND l.reconcile_id = r.id AND o.id = %s AND o.invoice_id = i.id",
- (order.id,))
+ if order.invoice_id:
+ cr.execute(" SELECT MAX(l.date) "
+ " FROM account_move_line l, account_move m, account_invoice i, account_move_reconcile r, pos_order o "
+ " WHERE i.move_id = m.id AND l.move_id = m.id AND l.reconcile_id = r.id AND o.id = %s AND o.invoice_id = i.id",
+ (order.id,))
+ else:
+ cr.execute("SELECT MAX(l.date) from account_move_line l "
+ "left join account_bank_statement abs on (l.statement_id=abs.id)"
+ "left join account_bank_statement_line absl on (absl.statement_id=abs.id) "
+ "left join pos_order p on (p.id=absl.pos_statement_id) "
+ "left join account_account a on (a.id=absl.account_id) "
+ "where p.id=%s and l.reconcile_id is not NULL and a.reconcile=True", (order.id,))
val = cr.fetchone()
val = val and val[0] or None
if val:
@@ -209,7 +217,8 @@ class pos_order(osv.osv):
'picking_id': False,
'statement_ids': [],
'nb_print': 0,
- 'pickings': []
+ 'pickings': [],
+ 'name': self.pool.get('ir.sequence').get(cr, uid, 'pos.order'),
})
return super(pos_order, self).copy(cr, uid, id, default, context=context)
@@ -1170,6 +1179,15 @@ class pos_order_line(osv.osv):
if 'product_id' in values and not values['product_id']:
return False
return super(pos_order_line, self).write(cr, user, ids, values, context=context)
+
+ def copy_data(self, cr, uid, id, default=None, context=None):
+ if not default:
+ default = {}
+ default.update({
+ 'prodlot_id': False,
+ 'name': self.pool.get('ir.sequence').get(cr, uid, 'pos.order.line')
+ })
+ return super(pos_order_line, self).copy_data(cr, uid, id, default, context=context)
def _scan_product(self, cr, uid, ean, qty, order):
# search pricelist_id
diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml
index 233bb019443..a8552cddc15 100644
--- a/addons/point_of_sale/point_of_sale_view.xml
+++ b/addons/point_of_sale/point_of_sale_view.xml
@@ -140,7 +140,7 @@
-
+
diff --git a/addons/point_of_sale/report/pos_order_report_view.xml b/addons/point_of_sale/report/pos_order_report_view.xml
index c72a6b6d415..8b18bf0b57a 100644
--- a/addons/point_of_sale/report/pos_order_report_view.xml
+++ b/addons/point_of_sale/report/pos_order_report_view.xml
@@ -46,14 +46,12 @@
help="POS ordered created during current month"/>
@@ -78,7 +76,7 @@
-
+
diff --git a/addons/point_of_sale/report/report_cash_register_view.xml b/addons/point_of_sale/report/report_cash_register_view.xml
index 167851986cb..8e42a9a5ce0 100644
--- a/addons/point_of_sale/report/report_cash_register_view.xml
+++ b/addons/point_of_sale/report/report_cash_register_view.xml
@@ -35,14 +35,12 @@
help="Cash Analysis created in current month"/>
@@ -61,7 +59,7 @@
-
+
diff --git a/addons/point_of_sale/test/point_of_sale_test.yml b/addons/point_of_sale/test/point_of_sale_test.yml
index 18498cb40c1..6165e993240 100644
--- a/addons/point_of_sale/test/point_of_sale_test.yml
+++ b/addons/point_of_sale/test/point_of_sale_test.yml
@@ -227,29 +227,14 @@
!record {model: product.category, id: product_category_services0}:
name: Mobile Services
-
- I create product template .
+ I create product Samsung Mobile.
-
- !record {model: product.template, id: product_template_lgviewtysmart0}:
+ !record {model: product.product, id: product_product_samsungmobile0}:
categ_id: product_category_allproductssellable0
cost_method: standard
list_price: 170.0
mes_type: fixed
- name: LG Viewty Smart
- procure_method: make_to_order
- standard_price: 160.0
- supply_method: produce
- type: product
- uom_id: product.product_uom_unit
- uom_po_id: product.product_uom_unit
--
- I create product LG Viewty Smart.
--
- !record {model: product.product, id: product_product_lgviewtysmart0}:
- categ_id: product_category_allproductssellable0
- cost_method: standard
- list_price: 170.0
- mes_type: fixed
- name: LG Viewty Smart
+ name: Samsung Mobile
procure_method: make_to_order
property_account_expense: account_pos_account_payable
property_account_income: account_pos_account_receivable
@@ -285,7 +270,7 @@
property_account_expense: account_pos_account_payable
property_account_income: account_pos_account_receivable
-
- I create New Order by adding the details of Salesman and "Product" as LG Viewty Smart Quantity as 10.
+ I create New Order by adding the details of Salesman and "Product" as Samsung Mobile Quantity as 10.
-
!record {model: pos.order, id: pos_order_pos0}:
company_id: base.main_company
@@ -295,7 +280,7 @@
- company_id: base.main_company
name: Order Line/01
notice: No Discount
- product_id: product_product_lgviewtysmart0
+ product_id: product_product_samsungmobile0
qty: 10.0
qty_rfd: 0.0
partner_id: base.res_partner_agrolait
diff --git a/addons/portal/i18n/es.po b/addons/portal/i18n/es.po
index 68040630e8c..f4fca48ead1 100644
--- a/addons/portal/i18n/es.po
+++ b/addons/portal/i18n/es.po
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
"POT-Creation-Date: 2011-07-04 14:16+0000\n"
-"PO-Revision-Date: 2011-07-11 09:51+0000\n"
-"Last-Translator: FULL NAME \n"
+"PO-Revision-Date: 2011-09-17 23:24+0000\n"
+"Last-Translator: mgaja (GrupoIsep.com) \n"
"Language-Team: Spanish \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-09-05 05:59+0000\n"
-"X-Generator: Launchpad (build 13830)\n"
+"X-Launchpad-Export-Date: 2011-09-19 04:41+0000\n"
+"X-Generator: Launchpad (build 13955)\n"
#. module: portal
#: code:addons/portal/wizard/share_wizard.py:55
@@ -32,7 +32,7 @@ msgstr ""
#. module: portal
#: field:res.portal,group_id:0
msgid "Group"
-msgstr ""
+msgstr "Grupo"
#. module: portal
#: help:res.portal,other_group_ids:0
@@ -43,7 +43,7 @@ msgstr ""
#: view:share.wizard:0
#: field:share.wizard,group_ids:0
msgid "Existing groups"
-msgstr ""
+msgstr "Grupos existentes"
#. module: portal
#: model:ir.model,name:portal.model_res_portal_wizard_user
@@ -63,7 +63,7 @@ msgstr ""
#. module: portal
#: field:res.portal.wizard.user,user_email:0
msgid "E-mail"
-msgstr ""
+msgstr "Correo electrónico"
#. module: portal
#: view:res.portal:0
@@ -74,12 +74,14 @@ msgstr ""
#: constraint:res.users:0
msgid "The chosen company is not in the allowed companies for this user"
msgstr ""
+"La compañía seleccionada no está en las compañías permitidas para este "
+"usuario"
#. module: portal
#: view:res.portal:0
#: field:res.portal,widget_ids:0
msgid "Widgets"
-msgstr ""
+msgstr "Widgets"
#. module: portal
#: model:ir.module.module,description:portal.module_meta_information
@@ -108,7 +110,7 @@ msgstr ""
#. module: portal
#: view:res.portal.wizard:0
msgid "Send Invitations"
-msgstr ""
+msgstr "Enviar invitaciones"
#. module: portal
#: help:res.portal,url:0
@@ -118,7 +120,7 @@ msgstr ""
#. module: portal
#: field:res.portal.widget,widget_id:0
msgid "Widget"
-msgstr ""
+msgstr "Widget"
#. module: portal
#: help:res.portal.wizard,message:0
@@ -153,12 +155,12 @@ msgstr ""
#. module: portal
#: field:res.portal,menu_action_id:0
msgid "Menu Action"
-msgstr ""
+msgstr "Acción de menú"
#. module: portal
#: field:res.portal.wizard.user,name:0
msgid "User Name"
-msgstr ""
+msgstr "Nombre de usuario"
#. module: portal
#: model:ir.model,name:portal.model_res_portal_widget
@@ -185,7 +187,7 @@ msgstr ""
#: code:addons/portal/portal.py:184
#, python-format
msgid "%s Menu"
-msgstr ""
+msgstr "Menú de %s"
#. module: portal
#: help:res.portal.wizard,portal_id:0
@@ -207,12 +209,12 @@ msgstr ""
#: view:share.wizard:0
#: field:share.wizard,user_ids:0
msgid "Existing users"
-msgstr ""
+msgstr "Usuarios existentes"
#. module: portal
#: field:res.portal.wizard.user,wizard_id:0
msgid "Wizard"
-msgstr ""
+msgstr "Asistente"
#. module: portal
#: help:res.portal.wizard.user,user_email:0
@@ -224,12 +226,12 @@ msgstr ""
#. module: portal
#: field:res.portal.wizard.user,lang:0
msgid "Language"
-msgstr ""
+msgstr "Idioma"
#. module: portal
#: field:res.portal,url:0
msgid "URL"
-msgstr ""
+msgstr "URL"
#. module: portal
#: view:res.portal:0
@@ -249,7 +251,7 @@ msgstr "Cancelar"
#. module: portal
#: view:res.portal:0
msgid "Website"
-msgstr ""
+msgstr "Sitio web"
#. module: portal
#: view:res.portal:0
@@ -266,17 +268,17 @@ msgstr ""
#: code:addons/portal/wizard/portal_wizard.py:135
#, python-format
msgid "Email required"
-msgstr ""
+msgstr "Correo electrónico requerido"
#. module: portal
#: model:ir.model,name:portal.model_res_users
msgid "res.users"
-msgstr ""
+msgstr "res.usuarios"
#. module: portal
#: constraint:res.portal.wizard.user:0
msgid "Invalid email address"
-msgstr ""
+msgstr "Dirección email invalida"
#. module: portal
#: code:addons/portal/wizard/portal_wizard.py:136
@@ -288,7 +290,7 @@ msgstr ""
#. module: portal
#: model:ir.model,name:portal.model_ir_ui_menu
msgid "ir.ui.menu"
-msgstr ""
+msgstr "ir.ui.menu"
#. module: portal
#: help:res.portal,group_id:0
@@ -300,7 +302,7 @@ msgstr ""
#: view:res.portal.wizard:0
#: field:res.portal.wizard,user_ids:0
msgid "Users"
-msgstr ""
+msgstr "Usuarios"
#. module: portal
#: field:res.portal,other_group_ids:0
@@ -323,7 +325,7 @@ msgstr ""
#. module: portal
#: field:res.portal.widget,sequence:0
msgid "Sequence"
-msgstr ""
+msgstr "Secuencia"
#. module: portal
#: field:res.users,partner_id:0
@@ -338,13 +340,13 @@ msgstr ""
#. module: portal
#: sql_constraint:res.users:0
msgid "You can not have two users with the same login !"
-msgstr ""
+msgstr "¡No puede tener dos usuarios con el mismo identificador de usuario!"
#. module: portal
#: view:res.portal.wizard:0
#: field:res.portal.wizard,message:0
msgid "Invitation message"
-msgstr ""
+msgstr "Mensaje de invitación"
#. module: portal
#: code:addons/portal/wizard/portal_wizard.py:36
@@ -386,7 +388,7 @@ msgstr ""
#. module: portal
#: field:res.portal.wizard.user,partner_id:0
msgid "Partner"
-msgstr ""
+msgstr "Empresa"
#. module: portal
#: model:ir.actions.act_window,help:portal.portal_list_action
@@ -402,4 +404,4 @@ msgstr ""
#. module: portal
#: model:ir.model,name:portal.model_share_wizard
msgid "Share Wizard"
-msgstr ""
+msgstr "Asistente de compartición"
diff --git a/addons/portal/wizard/portal_wizard.py b/addons/portal/wizard/portal_wizard.py
index a18ecb2c33e..b966abfef41 100644
--- a/addons/portal/wizard/portal_wizard.py
+++ b/addons/portal/wizard/portal_wizard.py
@@ -26,7 +26,7 @@ from osv import osv, fields
from tools.misc import email_re, email_send
from tools.translate import _
-from base.res.res_user import _lang_get
+from base.res.res_users import _lang_get
diff --git a/addons/process/process_view.xml b/addons/process/process_view.xml
index d158dd83425..de92b9c6c60 100644
--- a/addons/process/process_view.xml
+++ b/addons/process/process_view.xml
@@ -108,12 +108,12 @@
search
-
+
-
+
@@ -213,13 +213,13 @@
search
-
+
-
+
@@ -302,7 +302,7 @@
search
-
+
diff --git a/addons/procurement/i18n/da.po b/addons/procurement/i18n/da.po
new file mode 100644
index 00000000000..8f52d25c29a
--- /dev/null
+++ b/addons/procurement/i18n/da.po
@@ -0,0 +1,890 @@
+# Danish translation for openobject-addons
+# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2011-01-11 11:15+0000\n"
+"PO-Revision-Date: 2011-09-18 16:11+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Danish \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2011-09-19 04:41+0000\n"
+"X-Generator: Launchpad (build 13955)\n"
+
+#. module: procurement
+#: view:make.procurement:0
+msgid "Ask New Products"
+msgstr ""
+
+#. module: procurement
+#: model:ir.ui.menu,name:procurement.menu_stock_sched
+msgid "Schedulers"
+msgstr ""
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_make_procurement
+msgid "Make Procurements"
+msgstr ""
+
+#. module: procurement
+#: help:procurement.order.compute.all,automatic:0
+msgid ""
+"Triggers an automatic procurement for all products that have a virtual stock "
+"under 0. You should probably not use this option, we suggest using a MTO "
+"configuration on products."
+msgstr ""
+
+#. module: procurement
+#: view:stock.warehouse.orderpoint:0
+msgid "Group By..."
+msgstr "Gruppér efter..."
+
+#. module: procurement
+#: help:stock.warehouse.orderpoint,procurement_draft_ids:0
+msgid "Draft procurement of the product and location of that orderpoint"
+msgstr ""
+
+#. module: procurement
+#: code:addons/procurement/procurement.py:288
+#, python-format
+msgid "No supplier defined for this product !"
+msgstr ""
+
+#. module: procurement
+#: field:make.procurement,uom_id:0
+msgid "Unit of Measure"
+msgstr "Måleenhed"
+
+#. module: procurement
+#: field:procurement.order,procure_method:0
+msgid "Procurement Method"
+msgstr ""
+
+#. module: procurement
+#: code:addons/procurement/procurement.py:304
+#, python-format
+msgid "No address defined for the supplier"
+msgstr ""
+
+#. module: procurement
+#: model:ir.actions.act_window,name:procurement.action_procurement_compute
+msgid "Compute Stock Minimum Rules Only"
+msgstr ""
+
+#. module: procurement
+#: field:procurement.order,company_id:0
+#: field:stock.warehouse.orderpoint,company_id:0
+msgid "Company"
+msgstr "Virksomhed"
+
+#. module: procurement
+#: field:procurement.order,product_uos_qty:0
+msgid "UoS Quantity"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+#: field:procurement.order,name:0
+msgid "Reason"
+msgstr "Årsag"
+
+#. module: procurement
+#: view:procurement.order.compute:0
+msgid "Compute Procurements"
+msgstr ""
+
+#. module: procurement
+#: field:procurement.order,message:0
+msgid "Latest error"
+msgstr ""
+
+#. module: procurement
+#: help:mrp.property,composition:0
+msgid "Not used in computations, for information purpose only."
+msgstr ""
+
+#. module: procurement
+#: field:stock.warehouse.orderpoint,procurement_id:0
+msgid "Latest procurement"
+msgstr "Seneste indkøb"
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Notes"
+msgstr "Noter"
+
+#. module: procurement
+#: selection:procurement.order,procure_method:0
+msgid "on order"
+msgstr ""
+
+#. module: procurement
+#: help:procurement.order,message:0
+msgid "Exception occurred while computing procurement orders."
+msgstr ""
+
+#. module: procurement
+#: help:procurement.order,state:0
+msgid ""
+"When a procurement is created the state is set to 'Draft'.\n"
+" If the procurement is confirmed, the state is set to 'Confirmed'. "
+" \n"
+"After confirming the state is set to 'Running'.\n"
+" If any exception arises in the order then the state is set to 'Exception'.\n"
+" Once the exception is removed the state becomes 'Ready'.\n"
+" It is in 'Waiting'. state when the procurement is waiting for another one "
+"to finish."
+msgstr ""
+
+#. module: procurement
+#: view:stock.warehouse.orderpoint:0
+msgid "Minimum Stock Rules Search"
+msgstr ""
+
+#. module: procurement
+#: help:stock.warehouse.orderpoint,product_min_qty:0
+msgid ""
+"When the virtual stock goes belong the Min Quantity, OpenERP generates a "
+"procurement to bring the virtual stock to the Max Quantity."
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order.compute.all:0
+msgid "Scheduler Parameters"
+msgstr ""
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_stock_move
+msgid "Stock Move"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Planification"
+msgstr ""
+
+#. module: procurement
+#: selection:procurement.order,state:0
+msgid "Ready"
+msgstr "Klar"
+
+#. module: procurement
+#: field:procurement.order.compute.all,automatic:0
+msgid "Automatic orderpoint"
+msgstr ""
+
+#. module: procurement
+#: field:mrp.property,composition:0
+msgid "Properties composition"
+msgstr ""
+
+#. module: procurement
+#: selection:procurement.order,state:0
+msgid "Confirmed"
+msgstr "Bekræftet"
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Retry"
+msgstr "Forsøg igen"
+
+#. module: procurement
+#: view:procurement.order.compute:0
+#: view:procurement.orderpoint.compute:0
+msgid "Parameters"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Confirm"
+msgstr "Bekræft"
+
+#. module: procurement
+#: help:procurement.order,origin:0
+msgid ""
+"Reference of the document that created this Procurement.\n"
+"This is automatically completed by OpenERP."
+msgstr ""
+
+#. module: procurement
+#: view:stock.warehouse.orderpoint:0
+msgid "Procurement Orders to Process"
+msgstr ""
+
+#. module: procurement
+#: constraint:res.company:0
+msgid "Error! You can not create recursive companies."
+msgstr ""
+
+#. module: procurement
+#: field:procurement.order,priority:0
+msgid "Priority"
+msgstr "Prioritet"
+
+#. module: procurement
+#: view:procurement.order:0
+#: field:procurement.order,state:0
+msgid "State"
+msgstr "Stat"
+
+#. module: procurement
+#: field:procurement.order,location_id:0
+#: view:stock.warehouse.orderpoint:0
+#: field:stock.warehouse.orderpoint,location_id:0
+msgid "Location"
+msgstr "Placering"
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_stock_picking
+msgid "Picking List"
+msgstr ""
+
+#. module: procurement
+#: field:make.procurement,warehouse_id:0
+#: view:stock.warehouse.orderpoint:0
+#: field:stock.warehouse.orderpoint,warehouse_id:0
+msgid "Warehouse"
+msgstr "Varehus"
+
+#. module: procurement
+#: selection:stock.warehouse.orderpoint,logic:0
+msgid "Best price (not yet active!)"
+msgstr "Bedst pris (endnu ikke aktiv!)"
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Product & Location"
+msgstr ""
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_procurement_order_compute
+msgid "Compute Procurement"
+msgstr ""
+
+#. module: procurement
+#: model:ir.module.module,shortdesc:procurement.module_meta_information
+#: field:stock.move,procurements:0
+msgid "Procurements"
+msgstr "Indkøb"
+
+#. module: procurement
+#: field:res.company,schedule_range:0
+msgid "Scheduler Range Days"
+msgstr ""
+
+#. module: procurement
+#: model:ir.actions.act_window,help:procurement.procurement_action
+msgid ""
+"A procurement order is used to record a need for a specific product at a "
+"specific location. A procurement order is usually created automatically from "
+"sales orders, a Pull Logistics rule or Minimum Stock Rules. When the "
+"procurement order is confirmed, it automatically creates the necessary "
+"operations to fullfil the need: purchase order proposition, manufacturing "
+"order, etc."
+msgstr ""
+
+#. module: procurement
+#: field:make.procurement,date_planned:0
+msgid "Planned Date"
+msgstr "Planlagt dato"
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Group By"
+msgstr "Gruppér efter"
+
+#. module: procurement
+#: field:make.procurement,qty:0
+#: field:procurement.order,product_qty:0
+msgid "Quantity"
+msgstr "Mængde"
+
+#. module: procurement
+#: code:addons/procurement/procurement.py:377
+#, python-format
+msgid "Not enough stock and no minimum orderpoint rule defined."
+msgstr ""
+
+#. module: procurement
+#: code:addons/procurement/procurement.py:137
+#, python-format
+msgid "Invalid action !"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "References"
+msgstr ""
+
+#. module: procurement
+#: view:res.company:0
+msgid "Configuration"
+msgstr "Indstillinger"
+
+#. module: procurement
+#: field:stock.warehouse.orderpoint,qty_multiple:0
+msgid "Qty Multiple"
+msgstr ""
+
+#. module: procurement
+#: help:procurement.order,procure_method:0
+msgid ""
+"If you encode manually a Procurement, you probably want to use a make to "
+"order method."
+msgstr ""
+
+#. module: procurement
+#: model:ir.ui.menu,name:procurement.menu_stock_procurement
+msgid "Automatic Procurements"
+msgstr ""
+
+#. module: procurement
+#: field:stock.warehouse.orderpoint,product_max_qty:0
+msgid "Max Quantity"
+msgstr ""
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_procurement_order
+#: model:process.process,name:procurement.process_process_procurementprocess0
+#: view:procurement.order:0
+msgid "Procurement"
+msgstr "Indkøb"
+
+#. module: procurement
+#: model:ir.actions.act_window,name:procurement.procurement_action
+msgid "Procurement Orders"
+msgstr "Indkøbs ordre"
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "To Fix"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Exceptions"
+msgstr "Undtagelser"
+
+#. module: procurement
+#: model:process.node,note:procurement.process_node_serviceonorder0
+msgid "Assignment from Production or Purchase Order."
+msgstr ""
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_mrp_property
+msgid "Property"
+msgstr ""
+
+#. module: procurement
+#: model:ir.actions.act_window,name:procurement.act_make_procurement
+#: view:make.procurement:0
+msgid "Procurement Request"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.orderpoint.compute:0
+msgid "Compute Stock"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Late"
+msgstr ""
+
+#. module: procurement
+#: model:process.process,name:procurement.process_process_serviceproductprocess0
+msgid "Service"
+msgstr ""
+
+#. module: procurement
+#: model:ir.module.module,description:procurement.module_meta_information
+msgid ""
+"\n"
+" This is the module for computing Procurements.\n"
+" "
+msgstr ""
+
+#. module: procurement
+#: field:stock.warehouse.orderpoint,procurement_draft_ids:0
+msgid "Related Procurement Orders"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.orderpoint.compute:0
+msgid ""
+"Wizard checks all the stock minimum rules and generate procurement order."
+msgstr ""
+
+#. module: procurement
+#: field:stock.warehouse.orderpoint,product_min_qty:0
+msgid "Min Quantity"
+msgstr ""
+
+#. module: procurement
+#: selection:procurement.order,priority:0
+msgid "Urgent"
+msgstr "Haster"
+
+#. module: procurement
+#: selection:mrp.property,composition:0
+msgid "plus"
+msgstr ""
+
+#. module: procurement
+#: code:addons/procurement/procurement.py:325
+#, python-format
+msgid ""
+"Please check the Quantity in Procurement Order(s), it should not be less "
+"than 1!"
+msgstr ""
+
+#. module: procurement
+#: help:stock.warehouse.orderpoint,active:0
+msgid ""
+"If the active field is set to False, it will allow you to hide the "
+"orderpoint without removing it."
+msgstr ""
+
+#. module: procurement
+#: help:stock.warehouse.orderpoint,product_max_qty:0
+msgid ""
+"When the virtual stock goes belong the Max Quantity, OpenERP generates a "
+"procurement to bring the virtual stock to the Max Quantity."
+msgstr ""
+
+#. module: procurement
+#: help:procurement.orderpoint.compute,automatic:0
+msgid "If the stock of a product is under 0, it will act like an orderpoint"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Procurement Lines"
+msgstr "Indkøbs linier"
+
+#. module: procurement
+#: view:procurement.order.compute.all:0
+msgid ""
+"This wizard allows you to run all procurement, production and/or purchase "
+"orders that should be processed based on their configuration. By default, "
+"the scheduler is launched automatically every night by OpenERP. You can use "
+"this menu to force it to be launched now. Note that it runs in the "
+"background, you may have to wait for a few minutes until it has finished "
+"computing."
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+#: field:procurement.order,note:0
+msgid "Note"
+msgstr "Notat"
+
+#. module: procurement
+#: selection:procurement.order,state:0
+msgid "Draft"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order.compute:0
+msgid "This wizard will schedule procurements."
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Status"
+msgstr "Status"
+
+#. module: procurement
+#: selection:procurement.order,priority:0
+msgid "Normal"
+msgstr "Normal"
+
+#. module: procurement
+#: constraint:stock.move:0
+msgid "You try to assign a lot which is not from the same product"
+msgstr ""
+
+#. module: procurement
+#: field:stock.warehouse.orderpoint,active:0
+msgid "Active"
+msgstr "Aktiv"
+
+#. module: procurement
+#: model:process.node,name:procurement.process_node_procureproducts0
+msgid "Procure Products"
+msgstr ""
+
+#. module: procurement
+#: field:procurement.order,date_planned:0
+msgid "Scheduled date"
+msgstr ""
+
+#. module: procurement
+#: selection:procurement.order,state:0
+msgid "Exception"
+msgstr ""
+
+#. module: procurement
+#: code:addons/procurement/schedulers.py:179
+#, python-format
+msgid "Automatic OP: %s"
+msgstr ""
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_procurement_orderpoint_compute
+msgid "Automatic Order Point"
+msgstr ""
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_stock_warehouse_orderpoint
+msgid "Minimum Inventory Rule"
+msgstr ""
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_res_company
+msgid "Companies"
+msgstr "Virksomheder"
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Extra Information"
+msgstr ""
+
+#. module: procurement
+#: help:procurement.order,name:0
+msgid "Procurement name."
+msgstr ""
+
+#. module: procurement
+#: constraint:stock.move:0
+msgid "You must assign a production lot for this product"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Procurement Reason"
+msgstr ""
+
+#. module: procurement
+#: sql_constraint:stock.warehouse.orderpoint:0
+msgid "Qty Multiple must be greater than zero."
+msgstr ""
+
+#. module: procurement
+#: selection:stock.warehouse.orderpoint,logic:0
+msgid "Order to Max"
+msgstr ""
+
+#. module: procurement
+#: field:procurement.order,date_close:0
+msgid "Date Closed"
+msgstr ""
+
+#. module: procurement
+#: code:addons/procurement/procurement.py:372
+#, python-format
+msgid "Procurement '%s' is in exception: not enough stock."
+msgstr ""
+
+#. module: procurement
+#: code:addons/procurement/procurement.py:138
+#, python-format
+msgid "Cannot delete Procurement Order(s) which are in %s State!"
+msgstr ""
+
+#. module: procurement
+#: code:addons/procurement/procurement.py:324
+#, python-format
+msgid "Data Insufficient !"
+msgstr ""
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_mrp_property_group
+#: field:mrp.property,group_id:0
+#: field:mrp.property.group,name:0
+msgid "Property Group"
+msgstr ""
+
+#. module: procurement
+#: view:stock.warehouse.orderpoint:0
+msgid "Misc"
+msgstr "Diverse"
+
+#. module: procurement
+#: view:stock.warehouse.orderpoint:0
+msgid "Locations"
+msgstr ""
+
+#. module: procurement
+#: selection:procurement.order,procure_method:0
+msgid "from stock"
+msgstr ""
+
+#. module: procurement
+#: view:stock.warehouse.orderpoint:0
+msgid "General Information"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Run Procurement"
+msgstr ""
+
+#. module: procurement
+#: selection:procurement.order,state:0
+msgid "Done"
+msgstr ""
+
+#. module: procurement
+#: help:stock.warehouse.orderpoint,qty_multiple:0
+msgid "The procurement quantity will by rounded up to this multiple."
+msgstr ""
+
+#. module: procurement
+#: view:make.procurement:0
+#: view:procurement.order:0
+#: selection:procurement.order,state:0
+#: view:procurement.order.compute:0
+#: view:procurement.order.compute.all:0
+#: view:procurement.orderpoint.compute:0
+msgid "Cancel"
+msgstr "Annullér"
+
+#. module: procurement
+#: field:stock.warehouse.orderpoint,logic:0
+msgid "Reordering Mode"
+msgstr ""
+
+#. module: procurement
+#: field:procurement.order,origin:0
+msgid "Source Document"
+msgstr ""
+
+#. module: procurement
+#: selection:procurement.order,priority:0
+msgid "Not urgent"
+msgstr ""
+
+#. module: procurement
+#: model:ir.model,name:procurement.model_procurement_order_compute_all
+msgid "Compute all schedulers"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Current"
+msgstr "Nuværende"
+
+#. module: procurement
+#: view:board.board:0
+msgid "Procurements in Exception"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Details"
+msgstr ""
+
+#. module: procurement
+#: model:ir.actions.act_window,name:procurement.procurement_action5
+#: model:ir.actions.act_window,name:procurement.procurement_action_board
+#: model:ir.actions.act_window,name:procurement.procurement_exceptions
+#: model:ir.ui.menu,name:procurement.menu_stock_procurement_action
+msgid "Procurement Exceptions"
+msgstr ""
+
+#. module: procurement
+#: model:ir.actions.act_window,name:procurement.act_procurement_2_stock_warehouse_orderpoint
+#: model:ir.actions.act_window,name:procurement.act_product_product_2_stock_warehouse_orderpoint
+#: model:ir.actions.act_window,name:procurement.act_stock_warehouse_2_stock_warehouse_orderpoint
+#: model:ir.actions.act_window,name:procurement.action_orderpoint_form
+#: model:ir.ui.menu,name:procurement.menu_stock_order_points
+#: view:stock.warehouse.orderpoint:0
+msgid "Minimum Stock Rules"
+msgstr ""
+
+#. module: procurement
+#: field:procurement.order,close_move:0
+msgid "Close Move at end"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Scheduled Date"
+msgstr ""
+
+#. module: procurement
+#: field:make.procurement,product_id:0
+#: view:procurement.order:0
+#: field:procurement.order,product_id:0
+#: field:stock.warehouse.orderpoint,product_id:0
+msgid "Product"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Temporary"
+msgstr ""
+
+#. module: procurement
+#: field:mrp.property,description:0
+#: field:mrp.property.group,description:0
+msgid "Description"
+msgstr ""
+
+#. module: procurement
+#: selection:mrp.property,composition:0
+msgid "min"
+msgstr ""
+
+#. module: procurement
+#: view:stock.warehouse.orderpoint:0
+msgid "Quantity Rules"
+msgstr ""
+
+#. module: procurement
+#: selection:procurement.order,state:0
+msgid "Running"
+msgstr "Kører"
+
+#. module: procurement
+#: field:stock.warehouse.orderpoint,product_uom:0
+msgid "Product UOM"
+msgstr ""
+
+#. module: procurement
+#: model:process.node,name:procurement.process_node_serviceonorder0
+msgid "Make to Order"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "UOM"
+msgstr ""
+
+#. module: procurement
+#: selection:procurement.order,state:0
+msgid "Waiting"
+msgstr "Venter"
+
+#. module: procurement
+#: model:ir.actions.act_window,help:procurement.action_orderpoint_form
+msgid ""
+"You can define your minimum stock rules, so that OpenERP will automatically "
+"create draft manufacturing orders or purchase quotations according to the "
+"stock level. Once the virtual stock of a product (= stock on hand minus all "
+"confirmed orders and reservations) is below the minimum quantity, OpenERP "
+"will generate a procurement request to increase the stock up to the maximum "
+"quantity."
+msgstr ""
+
+#. module: procurement
+#: field:procurement.order,move_id:0
+msgid "Reservation"
+msgstr ""
+
+#. module: procurement
+#: model:process.node,note:procurement.process_node_procureproducts0
+msgid "The way to procurement depends on the product type."
+msgstr ""
+
+#. module: procurement
+#: view:make.procurement:0
+msgid ""
+"This wizard will plan the procurement for this product. This procurement may "
+"generate task, production orders or purchase orders."
+msgstr ""
+
+#. module: procurement
+#: view:res.company:0
+msgid "MRP & Logistics Scheduler"
+msgstr ""
+
+#. module: procurement
+#: field:mrp.property,name:0
+#: field:stock.warehouse.orderpoint,name:0
+msgid "Name"
+msgstr "Navn"
+
+#. module: procurement
+#: selection:mrp.property,composition:0
+msgid "max"
+msgstr "maks"
+
+#. module: procurement
+#: field:procurement.order,product_uos:0
+msgid "Product UoS"
+msgstr ""
+
+#. module: procurement
+#: code:addons/procurement/procurement.py:353
+#, python-format
+msgid "from stock: products assigned."
+msgstr ""
+
+#. module: procurement
+#: model:ir.actions.act_window,name:procurement.action_compute_schedulers
+#: model:ir.ui.menu,name:procurement.menu_stock_proc_schedulers
+#: view:procurement.order.compute.all:0
+msgid "Compute Schedulers"
+msgstr ""
+
+#. module: procurement
+#: model:ir.actions.act_window,help:procurement.procurement_exceptions
+msgid ""
+"Procurement Orders represent the need for a certain quantity of products, at "
+"a given time, in a given location. Sales Orders are one typical source of "
+"Procurement Orders (but these are distinct documents). Depending on the "
+"procurement parameters and the product configuration, the procurement engine "
+"will attempt to satisfy the need by reserving products from stock, ordering "
+"products from a supplier, or passing a manufacturing order, etc. A "
+"Procurement Exception occurs when the system cannot find a way to fulfill a "
+"procurement. Some exceptions will resolve themselves automatically, but "
+"others require manual intervention (those are identified by a specific error "
+"message)."
+msgstr ""
+
+#. module: procurement
+#: field:procurement.order,product_uom:0
+msgid "Product UoM"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Search Procurement"
+msgstr "Søg indkøb"
+
+#. module: procurement
+#: help:res.company,schedule_range:0
+msgid ""
+"This is the time frame analysed by the scheduler when computing "
+"procurements. All procurements that are not between today and today+range "
+"are skipped for future computation."
+msgstr ""
+
+#. module: procurement
+#: selection:procurement.order,priority:0
+msgid "Very Urgent"
+msgstr ""
+
+#. module: procurement
+#: field:procurement.orderpoint.compute,automatic:0
+msgid "Automatic Orderpoint"
+msgstr ""
+
+#. module: procurement
+#: view:procurement.order:0
+msgid "Procurement Details"
+msgstr ""
+
+#. module: procurement
+#: code:addons/procurement/schedulers.py:180
+#, python-format
+msgid "SCHEDULER"
+msgstr ""
diff --git a/addons/procurement/procurement_view.xml b/addons/procurement/procurement_view.xml
index 09dd890970a..b2f5e13b756 100644
--- a/addons/procurement/procurement_view.xml
+++ b/addons/procurement/procurement_view.xml
@@ -100,7 +100,7 @@
search
-
+
-
+
@@ -181,7 +181,7 @@
search
-
+
@@ -189,7 +189,7 @@
-
+
diff --git a/addons/procurement/schedulers.py b/addons/procurement/schedulers.py
index 3a6e3336472..d72651ed55a 100644
--- a/addons/procurement/schedulers.py
+++ b/addons/procurement/schedulers.py
@@ -77,7 +77,7 @@ class procurement_order(osv.osv):
report_later += 1
for proc in procurement_obj.browse(cr, uid, ids, context=context):
if proc.state == 'exception':
- report.append('PROC %d: on order - %3.2f %-5s - %s' % \
+ report.append(_('PROC %d: on order - %3.2f %-5s - %s') % \
(proc.id, proc.product_qty, proc.product_uom.name,
proc.product_id.name))
report_except += 1
@@ -100,7 +100,7 @@ class procurement_order(osv.osv):
report_total += 1
for proc in procurement_obj.browse(cr, uid, report_ids, context=context):
if proc.state == 'exception':
- report.append('PROC %d: from stock - %3.2f %-5s - %s' % \
+ report.append(_('PROC %d: from stock - %3.2f %-5s - %s') % \
(proc.id, proc.product_qty, proc.product_uom.name,
proc.product_id.name,))
report_except += 1
@@ -111,15 +111,15 @@ class procurement_order(osv.osv):
end_date = time.strftime('%Y-%m-%d, %Hh %Mm %Ss')
if uid:
request = self.pool.get('res.request')
- summary = '''Here is the procurement scheduling report.
+ summary = _("""Here is the procurement scheduling report.
- Start Time: %s
- End Time: %s
- Total Procurements processed: %d
- Procurements with exceptions: %d
- Skipped Procurements (scheduled date outside of scheduler range) %d
+ Start Time: %s
+ End Time: %s
+ Total Procurements processed: %d
+ Procurements with exceptions: %d
+ Skipped Procurements (scheduled date outside of scheduler range) %d
- Exceptions:\n'''% (start_date, end_date, report_total, report_except, report_later)
+ Exceptions:\n""") % (start_date, end_date, report_total, report_except, report_later)
summary += '\n'.join(report)
request.create(cr, uid,
{'name': "Procurement Processing Report.",
diff --git a/addons/product/product.py b/addons/product/product.py
index b81fdc0bad1..863081a8155 100644
--- a/addons/product/product.py
+++ b/addons/product/product.py
@@ -121,8 +121,7 @@ class product_uom(osv.osv):
}
_sql_constraints = [
- ('factor_gt_zero', 'CHECK (factor!=0)', 'The conversion ratio for a unit of measure cannot be 0!'),
- ('factor_category_id_uniq', 'unique (category_id, factor)', 'You can not have more than one UOM with same factor for same UOM category'),
+ ('factor_gt_zero', 'CHECK (factor!=0)', 'The conversion ratio for a unit of measure cannot be 0!')
]
def _compute_qty(self, cr, uid, from_uom_id, qty, to_uom_id=False):
@@ -230,7 +229,7 @@ class product_category(osv.osv):
return True
_constraints = [
- (_check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
+ (_check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id'])
]
def child_get(self, cr, uid, ids):
return [ids]
@@ -276,8 +275,8 @@ class product_template(osv.osv):
'list_price': fields.float('Sale Price', digits_compute=dp.get_precision('Sale Price'), help="Base price for computing the customer price. Sometimes called the catalog price."),
'standard_price': fields.float('Cost Price', required=True, digits_compute=dp.get_precision('Purchase Price'), help="Product's cost for accounting stock valuation. It is the base price for the supplier price."),
'volume': fields.float('Volume', help="The volume in m3."),
- 'weight': fields.float('Gross weight', help="The gross weight in Kg."),
- 'weight_net': fields.float('Net weight', help="The net weight in Kg."),
+ 'weight': fields.float('Gross weight', digits_compute=dp.get_precision('Stock Weight'), help="The gross weight in Kg."),
+ 'weight_net': fields.float('Net weight', digits_compute=dp.get_precision('Stock Weight'), help="The net weight in Kg."),
'cost_method': fields.selection([('standard','Standard Price'), ('average','Average Price')], 'Costing Method', required=True,
help="Standard Price: the cost price is fixed and recomputed periodically (usually at the end of the year), Average Price: the cost price is recomputed at each reception of products."),
'warranty': fields.float('Warranty (months)'),
diff --git a/addons/product/product_demo.xml b/addons/product/product_demo.xml
index d8c1e29749b..3aebec13715 100644
--- a/addons/product/product_demo.xml
+++ b/addons/product/product_demo.xml
@@ -129,7 +129,7 @@
300.0
- 2.0
+ 7.0Basic PCproduct
@@ -154,7 +154,7 @@
600.0
- 3.0
+ 7.0Medium PCproduct
@@ -178,7 +178,7 @@
54.0
- 1.0
+ 7.0Mainboard ASUStek A7N8Xproduct
@@ -189,7 +189,7 @@
180.0
- 1.0
+ 7.0Mainboard ASUStek A7V8X-Xproduct
@@ -201,7 +201,7 @@
50.0
- 1.0
+ 7.0Processor AMD Athlon XP 1800+product
@@ -212,7 +212,7 @@
100.0
- 1.0
+ 7.0Processor AMD Athlon XP 2200+product
@@ -223,7 +223,7 @@
40.0
- 1.0
+ 7.0HDD Seagate 7200.8 80GBproduct
@@ -234,7 +234,7 @@
60.0
- 1.0
+ 7.0HDD Seagate 7200.8 120GBproduct
@@ -246,7 +246,7 @@
make_to_order
- 1.0
+ 7.0HDD Seagate 7200.8 160GBproduct
@@ -257,7 +257,7 @@
5.0
- 1.0
+ 7.0Regular case fan 80mmproduct
@@ -268,7 +268,7 @@
10.0
- 1.0
+ 7.0Silent fanproduct
@@ -280,7 +280,7 @@
200.0
- 1.0
+ 7.0Regular processor configmake_to_orderproduct
@@ -292,7 +292,7 @@
40.0
- 1.0
+ 7.0DDR 256MB PC400product
@@ -303,7 +303,7 @@
60.0
- 1.0
+ 7.0DDR 512MB PC400product
@@ -316,7 +316,7 @@
make_to_order
- 1.0
+ 7.0High speed processor configproduct
@@ -327,7 +327,7 @@
25.0
- 1.0
+ 7.0ATX Mid-size Towerproduct
@@ -340,7 +340,7 @@
make_to_order
- 1.0
+ 7.0HDD on demandproduct
@@ -449,7 +449,7 @@
Wood 2mm
-
+ product
@@ -515,7 +515,7 @@
Rear Panel SHE100
-
+ product
@@ -528,7 +528,7 @@
Rear Panel SHE200
-
+ product
@@ -541,7 +541,7 @@
Side Panel
-
+ product
@@ -554,7 +554,7 @@
Rack 100cm
-
+ product
@@ -567,7 +567,7 @@
Rack 200cm
-
+ product
@@ -593,7 +593,7 @@
Wood Lintel 4m
-
+ product
@@ -605,7 +605,7 @@
Metal Cleats
-
+ product
@@ -736,21 +736,21 @@
11
- 10
+ 511
- 10
+ 511
- 10
+ 5
@@ -758,7 +758,7 @@
15
- 10
+ 5
@@ -766,7 +766,7 @@
15
- 10
+ 5
@@ -774,7 +774,7 @@
15
- 10
+ 5
+
+ product_pulled_flow multi-company
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
+
+ stock_location_path multi-company
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
+
+
diff --git a/addons/stock_planning/__openerp__.py b/addons/stock_planning/__openerp__.py
index bb726d1013b..eb3cb385078 100644
--- a/addons/stock_planning/__openerp__.py
+++ b/addons/stock_planning/__openerp__.py
@@ -20,94 +20,129 @@
##############################################################################
{
- "name":"Master Procurement Schedule",
- "version":"1.1",
- "author":["OpenERP SA", "Grzegorz Grzelak (Cirrus)"],
- "category":"Warehouse",
- "images":["images/master_procurement_schedule.jpeg","images/sales_forecast.jpeg","images/stock_planning_line.jpeg","images/stock_sales_period.jpeg"],
- "depends":["hr","stock","sale"],
+ "name": "Master Procurement Schedule",
+ "version": "1.2",
+ "author": "OpenERP SA and Grzegorz Grzelak (OpenGLOBE)",
+ "category": "Warehouse",
+ "images": ["images/master_procurement_schedule.jpeg","images/sales_forecast.jpeg","images/stock_planning_line.jpeg","images/stock_sales_period.jpeg"],
+ "depends": ["crm", "stock","sale"],
+ "complexity": "expert",
"description": """
-Purpose of MPS is to allow create a manual procurement apart of MRP scheduler (which works automatically on minimum stock rules).
-=================================================================================================================================
+MPS allows to create a manual procurement plan apart of the normal MRP scheduling, which works automatically based on minimum stock rules
+=========================================================================================================================================
-This module is based on original OpenERP SA module stock_planning version 1.0 of the same name Master Procurement Schedule.
-Terms used in the module:
-- Stock and Sales Period - is the time (between Start Date and End Date) for which you plan Stock and Sales Forecast and make Procurement Planning.
-- Stock and Sales Forecast - is the quantity of products you plan to sell in the Period.
-- Stock Planning - is the quantity of products you plan to purchase or produce for the Period.
+Quick Glossary
+--------------
+- Stock Period - the time boundaries (between Start Date and End Date) for your Sales and Stock forecasts and planning
+- Sales Forecast - the quantity of products you plan to sell during the related Stock Period.
+- Stock Planning - the quantity of products you plan to purchase or produce for the related Stock Period.
-Because we have another module sale_forecast which uses terms "Sales Forecast" and "Planning" as amount values we will use terms "Stock and Sales Forecast" and "Stock Planning" to emphasize that we use quantity values.
+To avoid confusion with the terms used by the ``sale_forecast`` module, ("Sales Forecast" and "Planning" are amounts) we use terms "Stock and Sales Forecast" and "Stock Planning" to emphasize that we use quantity values.
-Activity with this module is divided to three steps:
-- Creating Periods. Mandatory step.
-- Creating Sale Forecasts and entering quantities to them. Optional step but useful for further planning.
-- Creating Planning lines, entering quantities to them and making Procurement. Making procurement is the final step for the Period.
+Where to begin
+--------------
+Using this module is done in three steps:
-Periods
--------
-You have two menu items for Periods in "Sales Management - Configuration". There are:
-- "Create Sales Periods" - Which automates creating daily, weekly or monthly periods.
-- "Stock and sales Periods" - Which allows to create any type of periods, change the dates and change the State of period.
+ * Create Stock Periods via the Warehouse>Configuration>Stock Periods menu (Mandatory step)
+ * Create Sale Forecasts fill them with forecast quantities, via the Sales>Sales Forecast menu. (Optional step but useful for further planning)
+ * Create the actual MPS plan, check the balance and trigger the procurements as required. The actual procurement is the final step for the Stock Period.
-Creating periods is the first step you have to do to use modules features. You can create custom periods using "New" button in "Stock and Sales Periods" form or view but it is recommended to use automating tool.
+Stock Period configuration
+--------------------------
+You have two menu items for Periods in "Warehouse > Configuration > Stock Periods". There are:
-Remarks:
-- These periods (officially Stock and Sales Periods) are separated of Financial or other periods in the system.
-- Periods are not assigned to companies (when you use multicompany feature at all). Module suppose that you use the same periods across companies. If you wish to use different periods for different companies define them as you wish (they can overlap). Later on in this text will be indications how to use such periods.
-- When periods are created automatically their start and finish dates are with start hour 00:00:00 and end hour 23:59:00. when you create daily periods they will have start date 31.01.2010 00:00:00 and end date 31.01.2010 23:59:00. It works only in automatic creation of periods. When you create periods manually you have to take care about hours because you can have incorrect values form sales or stock.
-- If you use overlapping periods for the same product, warehouse and company results can be unpredictable.
-- If current date doesn't belong to any period or you have holes between periods results can be unpredictable.
+ * "Create Stock Periods" - can automatically creating daily, weekly or monthly periods.
+ * "Stock Periods" - allows to create any type of periods, change the dates and change the state of period.
-Sales Forecasts
----------------
-You have few menus for Sales forecast in "Sales Management - Sales Forecasts".
-- "Create Sales Forecasts for Sales Periods" - which automates creating forecasts lines according to some parameters.
-- "Sales Forecasts" - few menus for working with forecasts lists and forms.
-
-Menu "Create Sales Forecasts for Sales Periods" creates Forecasts for products from selected Category, for selected Period and for selected Warehouse. It is an option "Copy Last Forecast" to copy forecast and other settings of period before this one to created one.
-
-Remarks:
-- This tool doesn't create lines, if relevant lines (for the same Product, Period, Warehouse and validated or created by you) already exists. If you wish to create another forecast, if relevant lines exists you have to do it manually using menus described bellow.
-- When created lines are validated by someone else you can use this tool to create another lines for the same Period, Product and Warehouse.
-- When you choose "Copy Last Forecast" created line takes quantity and some settings from your (validated by you or created by you if not validated yet) forecast which is for last period before period of created forecast. If there are few your forecasts for period before this one (it is possible) system takes one of them (no rule which of them).
-
-
-Menus "Sales Forecasts"
-On "Sales Forecast" form mainly you have to enter a forecast quantity in "Product Quantity". Further calculation can work for draft forecasts. But validation can save your data against any accidental changes. You can click "Validate" button but it is not mandatory.
-
-Instead of forecast quantity you can enter amount of forecast sales in field "Product Amount". System will count quantity from amount according to Sale price of the Product.
-
-All values on the form are expressed in unit of measure selected on form. You can select one of unit of measure from default category or from second category. When you change unit of measure the quanities will be recalculated according to new UoM: editable values (blue fields) immediately, non edited fields after clicking of "Calculate Planning" button.
-
-To find proper value for Sale Forecast you can use "Sales History" table for this product. You have to enter parameters to the top and left of this table and system will count sale quantities according to these parameters. So you can select your department (at the top) then (to the left): last period, period before last and period year ago.
+Creating periods is the first step. You can create custom periods using the "New" button in "Stock Periods", but it is recommended to use the automatic assistant "Create Stock Periods".
Remarks:
+ - These periods (Stock Periods) are completely distinct from Financial or other periods in the system.
+ - Periods are not assigned to companies (when you use multicompany). Module suppose that you use the same periods across companies. If you wish to use different periods for different companies define them as you wish (they can overlap). Later on in this text will be indications how to use such periods.
+ - When periods are created automatically their start and finish dates are with start hour 00:00:00 and end hour 23:59:00. When you create daily periods they will have start date 31.01.2010 00:00:00 and end date 31.01.2010 23:59:00. It works only in automatic creation of periods. When you create periods manually you have to take care about hours because you can have incorrect values form sales or stock.
+ - If you use overlapping periods for the same product, warehouse and company results can be unpredictable.
+ - If current date doesn't belong to any period or you have holes between periods results can be unpredictable.
-Procurement Planning
---------------------
-Menu for Planning you can find in "Warehouse - Stock Planning".
-- "Create Stock Planning Lines" - allows you to automate creating planning lines according to some parameters.
-- "Master Procurement Scheduler" - is the most important menu of the module which allows to create procurement.
+Sales Forecasts configuration
+-----------------------------
+You have few menus for Sales forecast in "Sales > Sales Forecasts":
-As Sales forecast is phase of planning sales. The Procurement Planning (Planning) is the phase of scheduling Purchasing or Producing. You can create Procurement Planning quickly using tool from menu "Create Stock Planning Lines", then you can review created planning and make procurement using menu "Master Procurement Schedule".
+ - "Create Sales Forecasts" - can automatically create forecast lines according to your needs
+ - "Sales Forecasts" - for managing the Sales forecasts
-Menu "Create Stock Planning Lines" allows you to create quickly Planning lines for products from selected Category, for selected Period, and for selected Warehouse. When you check option "All Products with Forecast" system creates lines for all products having forecast for selected Period and Warehouse. Selected Category will be ignored in this case.
+Menu "Create Sales Forecasts" creates Forecasts for products from selected Category, for selected Period and for selected Warehouse.
+It is also possible to copy the previous forecast.
-Under menu "Master Procurement Scheduler" you can generally change the values "Planned Out" and "Planned In" to observe the field "Stock Simulation" and decide if this value would be accurate for end of the Period.
-"Planned Out" can be based on "Warehouse Forecast" which is the sum of all forecasts for Period and Warehouse. But your planning can be based on any other information you have. It is not necessary to have any forecast.
-"Planned In" quantity is used to calculate field "Incoming Left" which is the quantity to be procured to make stock as indicated in "Stock Simulation" at the end of Period. You can compare "Stock Simulation" quantity to minimum stock rules visible on the form. But you can plan different quantity than in Minimum Stock Rules. Calculation is made for whole Warehouse by default. But if you want to see values for Stock location of calculated warehouse you can use check box "Stock Location Only".
+Remarks:
-If after few tries you decide that you found correct quantities for "Planned Out" and "Planned In" and you are satisfied with end of period stock calculated in "Stock Simulation" you can click "Procure Incoming Left" button to procure quantity of field "Incoming Left" into the Warehouse. System creates appropriate Procurement Order. You can decide if procurement will be made to Stock or Input location of calculated Warehouse.
+ - This tool doesn't duplicate lines if you already have an entry for the same Product, Period, Warehouse, created or validated by the same user. If you wish to create another forecast, if relevant lines exists you have to do it manually as described below.
+ - When created lines are validated by someone else you can use this tool to create another line for the same Period, Product and Warehouse.
+ - When you choose "Copy Last Forecast", created line take quantity and other settings from your (validated by you or created by you if not validated yet) forecast which is for last period before period of created forecast.
-If you don't want to Produce or Buy the product but just pick calculated quantity from another warehouse you can click "Supply from Another Warehouse" (instead of "Procure Incoming Left"). System creates pick list with move from selected source Warehouse to calculated Warehouse (as destination). You can also decide if this pick should be done from Stock or Output location of source warehouse. Destination location (Stock or Input) of destination warehouse will be used as set for "Procure Incoming Left".
+On "Sales Forecast" form mainly you have to enter a forecast quantity in "Product Quantity".
+Further calculation can work for draft forecasts. But validation can save your data against any accidental changes.
+You can click "Validate" button but it is not mandatory.
-To see proper quantities in fields "Confirmed In", "Confirmed Out", "Confirmed In Before", "Planned Out Before" and "Stock Simulation" you have to click button "Calculate Planning".
+Instead of forecast quantity you may enter the amount of forecast sales via the "Product Amount" field.
+The system will count quantity from amount according to Sale price of the Product.
-All values on the form are expressed in unit of measure selected on form. You can select one of unit of measure from default category or from second category. When you change unit of measure the quanities will be recalculated according to new UoM: editable values (blue fields) immediately, non edited fields after clicking of "Calculate Planning" button.
+All values on the form are expressed in unit of measure selected on form.
+You can select a unit of measure from the default category or from secondary category.
+When you change unit of measure the forecast product quantity will be re-computed according to new UoM.
-How Stock Simulation field is calculated:
-Generally Stock Simulation shows the stock for end of the calculated period according to some planned or confirmed stock moves. Calculation always starts with quantity of real stock of beginning of current period. Then calculation adds or subtracts quantities of calculated period or periods before calculated.
+To work out your Sale Forecast you can use the "Sales History" of the product.
+You have to enter parameters to the top and left of this table and system will count sale quantities according to these parameters.
+So you can get results for a given sales team or period.
+
+
+MPS or Procurement Planning
+---------------------------
+An MPS planning consists in Stock Planning lines, used to analyze and possibly drive the procurement of
+products for each relevant Stock Period and Warehouse.
+The menu is located in "Warehouse > Schedulers > Master Procurement Schedule":
+
+ - "Create Stock Planning Lines" - a wizard to help automatically create many planning lines
+ - "Master Procurement Schedule" - management of your planning lines
+
+Similarly to the way Sales forecast serves to define your sales planning, the MPS lets you plan your procurements (Purchase/Manufacturing).
+You can quickly populate the MPS with the "Create Stock Planning Lines" wizard, and then proceed to review them via the "Master Procurement Schedule" menu.
+
+The "Create Stock Planning Lines" wizard lets you to quickly create all MPS lines for a given Product Category, and a given Period and Warehouse.
+When you enable the "All Products with Forecast" option of the wizard, the system creates lines for all products having sales forecast for selected
+Period and Warehouse (the selected Category will be ignored in this case).
+
+Under menu "Master Procurement Schedule" you will usually change the "Planned Out" and "Planned In" quantities and observe the resulting "Stock Simulation" value
+to decide if you need to procure more products for the given Period.
+"Planned Out" will be initially based on "Warehouse Forecast" which is the sum of all outgoing stock moves already planned for the Period and Warehouse.
+Of course you can alter this value to provide your own quantities. It is not necessary to have any forecast.
+"Planned In" quantity is used to calculate field "Incoming Left" which is the quantity to be procured to reach the "Stock Simulation" at the end of Period.
+You can compare "Stock Simulation" quantity to minimum stock rules visible on the form.
+And you can plan different quantity than in Minimum Stock Rules. Calculations are done for whole Warehouse by default,
+if you want to see values for Stock location of calculated warehouse you can check "Stock Location Only".
+
+When you are satisfied with the "Planned Out", "Planned In" and end of period "Stock Simulation",
+you can click on "Procure Incoming Left" to create a procurement for the "Incoming Left" quantity.
+You can decide if procurement will go to the to Stock or Input location of the Warehouse.
+
+If you don't want to Produce or Buy the product but just transfer the calculated quantity from another warehouse
+you can click "Supply from Another Warehouse" (instead of "Procure Incoming Left") and the system will
+create the appropriate picking list (stock moves).
+You can choose to take the goods from the Stock or the Output location of the source warehouse.
+Destination location (Stock or Input) in the destination warehouse will be taken as for the procurement case.
+
+To see update the quantities of "Confirmed In", "Confirmed Out", "Confirmed In Before", "Planned Out Before"
+and "Stock Simulation" you can press "Calculate Planning".
+
+All values on the form are expressed in unit of measure selected on form.
+You can select one of unit of measure from default category or from secondary category.
+When you change unit of measure the editable quantities will be re-computed according to new UoM. The others will be updated after pressing "Calculate Planning".
+
+Computation of Stock Simulation quantities
+------------------------------------------
+The Stock Simulation value is the estimated stock quantity at the end of the period.
+The calculation always starts with the real stock on hand at the beginning of the current period, then
+adds or subtracts the computed quantities.
When you are in the same period (current period is the same as calculated) Stock Simulation is calculated as follows:
Stock Simulation =
@@ -124,9 +159,12 @@ Stock Simulation =
- Planned Out of calculated Period
+ Planned In of calculated Period .
-As you see calculated Period is taken the same way like in case above. But calculation of current Period are made a little bit different. First you should note that system takes for current Period only Confirmed In moves. It means that you have to make planning and procurement for current Period before this calculation (for Period next to current).
+As you see the calculated Period is taken the same way as in previous case, but the calculation in the current
+Period is a little bit different. First you should note that system takes for only Confirmed moves for the
+current period. This means that you should complete the planning and procurement of the current Period before
+going to the next one.
-When you calculate Period ahead:
+When you plan for future Periods:
Stock Simulation =
Stock of beginning of current Period
@@ -135,15 +173,23 @@ Stock Simulation =
- Planned Out of calculated Period
+ Planned In of calculated Period.
-Periods before calculated means periods starting from current till period before calculated.
+Here "Periods before calculated" designates all periods starting with the current until the period before the one being calculated.
Remarks:
-- Remember to make planning for all periods before calculated because omitting these quantities and procurements can cause wrong suggestions for procurements few periods ahead.
-- If you made planning few periods ahead and you find that real Confirmed Out is bigger than Planned Out in some periods before you can repeat Planning and make another procurement. You should do it in the same planning line. If you create another planning line the suggestions can be wrong.
-- When you wish to work with different periods for some part of products define two kinds of periods (fe. Weekly and Monthly) and use them for different products. Example: If you use always Weekly periods for Products A, and Monthly periods for Products B your all calculations will work correctly. You can also use different kind of periods for the same products from different warehouse or companies. But you cannot use overlapping periods for the same product, warehouse and company because results can be unpredictable. The same apply to Forecasts lines.
+
+ - Remember to make the proceed with the planning of each period in chronological order, otherwise the numbers will not reflect the
+ reality
+ - If you planned for future periods and find that real Confirmed Out is larger than Planned Out in some periods before,
+ you can repeat Planning and make another procurement. You should do it in the same planning line.
+ If you create another planning line the suggestions can be wrong.
+ - When you wish to work with different periods for some products, define two kinds of periods (e.g. Weekly and Monthly) and use
+ them for different products. Example: If you use always Weekly periods for Product A, and Monthly periods for Product B
+ all calculations will work correctly. You can also use different kind of periods for the same product from different warehouse
+ or companies. But you cannot use overlapping periods for the same product, warehouse and company because results
+ can be unpredictable. The same applies to Forecasts lines.
""",
- "demo_xml":[],
- "update_xml": [
+ "data": [
+ "security/stock_planning_security.xml",
"security/ir.model.access.csv",
"stock_planning_view.xml",
"wizard/stock_planning_create_periods_view.xml",
diff --git a/addons/stock_planning/security/stock_planning_security.xml b/addons/stock_planning/security/stock_planning_security.xml
new file mode 100644
index 00000000000..62f15e005bb
--- /dev/null
+++ b/addons/stock_planning/security/stock_planning_security.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ stock_planning multi-company
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
+
+ stock_sale_forecast multi-company
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
+
+
diff --git a/addons/stock_planning/stock_planning.py b/addons/stock_planning/stock_planning.py
index 7e9342292b9..fe0b2f8f3ce 100644
--- a/addons/stock_planning/stock_planning.py
+++ b/addons/stock_planning/stock_planning.py
@@ -26,6 +26,10 @@ from dateutil.relativedelta import relativedelta
from osv import osv, fields
import netsvc
from tools.translate import _
+import logging
+
+_logger = logging.getLogger('mps')
+
def rounding(fl, round_value):
if not round_value:
@@ -37,6 +41,7 @@ def rounding(fl, round_value):
class stock_period(osv.osv):
_name = "stock.period"
_description = "stock period"
+ _order = "date_start"
_columns = {
'name': fields.char('Period Name', size=64, required=True),
'date_start': fields.datetime('Start Date', required=True),
@@ -75,16 +80,16 @@ class stock_sale_forecast(osv.osv):
help = 'Shows which period this forecast concerns.'),
'product_id': fields.many2one('product.product', 'Product', readonly=True, required=True, states={'draft':[('readonly',False)]}, \
help = 'Shows which product this forecast concerns.'),
- 'product_qty': fields.float('Product Quantity', required=True, readonly=True, states={'draft':[('readonly',False)]}, \
- help= 'Forecasted quantity.'),
+ 'product_qty': fields.float('Forecast Quantity', required=True, readonly=True, states={'draft':[('readonly',False)]}, \
+ help= 'Forecast Product quantity.'),
'product_amt': fields.float('Product Amount', readonly=True, states={'draft':[('readonly',False)]}, \
help='Forecast value which will be converted to Product Quantity according to prices.'),
'product_uom_categ': fields.many2one('product.uom.categ', 'Product UoM Category'), # Invisible field for product_uom domain
'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, readonly=True, states={'draft':[('readonly',False)]}, \
- help = "Unit of Measure used to show the quanities of stock calculation." \
+ help = "Unit of Measure used to show the quantities of stock calculation." \
"You can use units form default category or from second category (UoS category)."),
'product_uos_categ' : fields.many2one('product.uom.categ', 'Product UoS Category'), # Invisible field for product_uos domain
-# Field used in onchange_uom to check what uom was before change and recalculate quantities acording to old uom (active_uom) and new uom.
+# Field used in onchange_uom to check what uom was before change and recalculate quantities according to old uom (active_uom) and new uom.
'active_uom': fields.many2one('product.uom', string = "Active UoM"),
'state': fields.selection([('draft','Draft'),('validated','Validated')],'State',readonly=True),
'analyzed_period1_id': fields.many2one('stock.period', 'Period1', readonly=True, states={'draft':[('readonly',False)]},),
@@ -93,7 +98,7 @@ class stock_sale_forecast(osv.osv):
'analyzed_period4_id': fields.many2one('stock.period', 'Period4', readonly=True, states={'draft':[('readonly',False)]},),
'analyzed_period5_id': fields.many2one('stock.period' , 'Period5', readonly=True, states={'draft':[('readonly',False)]},),
'analyzed_user_id': fields.many2one('res.users', 'This User', required=False, readonly=True, states={'draft':[('readonly',False)]},),
- 'analyzed_dept_id': fields.many2one('hr.department', 'This Department', required=False, \
+ 'analyzed_team_id': fields.many2one('crm.case.section', 'Sales Team', required=False, \
readonly=True, states={'draft':[('readonly',False)]},),
'analyzed_warehouse_id': fields.many2one('stock.warehouse' , 'This Warehouse', required=False, \
readonly=True, states={'draft':[('readonly',False)]}),
@@ -114,7 +119,7 @@ class stock_sale_forecast(osv.osv):
'analyzed_period3_per_warehouse': fields.float('This Warehouse Period3', readonly=True),
'analyzed_period4_per_warehouse': fields.float('This Warehouse Period4', readonly=True),
'analyzed_period5_per_warehouse': fields.float('This Warehouse Period5', readonly=True),
- 'analyzed_period1_per_company': fields.float('This Copmany Period1', readonly=True),
+ 'analyzed_period1_per_company': fields.float('This Company Period1', readonly=True),
'analyzed_period2_per_company': fields.float('This Company Period2', readonly=True),
'analyzed_period3_per_company': fields.float('This Company Period3', readonly=True),
'analyzed_period4_per_company': fields.float('This Company Period4', readonly=True),
@@ -137,7 +142,7 @@ class stock_sale_forecast(osv.osv):
if t['state'] in ('draft'):
unlink_ids.append(t['id'])
else:
- raise osv.except_osv(_('Invalid action !'), _('Cannot delete Validated Sale Forecasts !'))
+ raise osv.except_osv(_('Invalid action !'), _('Cannot delete a validated sales forecast!'))
osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
return True
@@ -147,7 +152,7 @@ class stock_sale_forecast(osv.osv):
return result
result['warehouse_id'] = False
result['analyzed_user_id'] = False
- result['analyzed_dept_id'] = False
+ result['analyzed_team_id'] = False
result['analyzed_warehouse_id'] = False
return {'value': result}
@@ -196,7 +201,7 @@ class stock_sale_forecast(osv.osv):
uom = uom_obj.browse(cr, uid, uom_id, context=context)
coef = uom.factor
if uom.category_id.id <> product.uom_id.category_id.id:
- coef = coef / product.uos_coeff
+ coef = coef * product.uos_coeff
return product.uom_id.factor / coef
def _from_default_uom_factor(self, cr, uid, product_id, uom_id, context=None):
@@ -206,7 +211,7 @@ class stock_sale_forecast(osv.osv):
uom = uom_obj.browse(cr, uid, uom_id, context=context)
res = uom.factor
if uom.category_id.id <> product.uom_id.category_id.id:
- res = res / product.uos_coeff
+ res = res * product.uos_coeff
return res / product.uom_id.factor, uom.rounding
def _sales_per_users(self, cr, uid, so, so_line, company, users):
@@ -237,35 +242,24 @@ class stock_sale_forecast(osv.osv):
so_line_obj = self.pool.get('sale.order.line')
so_line_product_ids = so_line_obj.search(cr, uid, [('product_id','=', obj.product_id.id)], context = context)
if so_line_product_ids:
- so_line_product_set = ','.join(map(str,so_line_product_ids))
+ shops = users = None
if obj.analyzed_warehouse_id:
shops = self.pool.get('sale.shop').search(cr, uid,[('warehouse_id','=', obj.analyzed_warehouse_id.id)], context = context)
- shops_set = ','.join(map(str,shops))
- else:
- shops = False
- if obj.analyzed_dept_id:
- dept_obj = self.pool.get('hr.department')
- dept_id = obj.analyzed_dept_id.id and [obj.analyzed_dept_id.id] or []
- dept_ids = dept_obj.search(cr,uid,[('parent_id','child_of',dept_id)])
- cr.execute("SELECT user_id FROM resource_resource rsc, hr_employee emp WHERE (emp.resource_id = rsc.id and emp.department_id IN %s)" ,(tuple(dept_ids),))
- dept_users = [x for x, in cr.fetchall()]
- dept_users_set = map(str,dept_users)
- else:
- dept_users = False
- factor, round_value = self._from_default_uom_factor(cr, uid, obj.product_id.id, obj.product_uom.id, context=context)
+ if obj.analyzed_team_id:
+ users = [u.id for u in obj.analyzed_team_id.member_ids]
+ factor, _ = self._from_default_uom_factor(cr, uid, obj.product_id.id, obj.product_uom.id, context=context)
for i, period in enumerate(periods):
if period:
so_period_ids = so_obj.search(cr, uid, [('date_order','>=',period.date_start),('date_order','<=',period.date_stop) ], context = context)
if so_period_ids:
if obj.analyzed_user_id:
- user_set = str(obj.analyzed_user_id.id)
- sales[i][0] = self._sales_per_users(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, user_set)
+ sales[i][0] = self._sales_per_users(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, [obj.analyzed_user_id.id])
sales[i][0] *= factor
- if dept_users:
- sales[i][1] = self._sales_per_users(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, dept_users_set)
+ if users:
+ sales[i][1] = self._sales_per_users(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, users)
sales[i][1] *= factor
if shops:
- sales[i][2] = self._sales_per_warehouse(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, shops_set)
+ sales[i][2] = self._sales_per_warehouse(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, shops)
sales[i][2] *= factor
if obj.analyze_company:
sales[i][3] = self._sales_per_company(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, )
@@ -343,13 +337,20 @@ class stock_planning(osv.osv):
GROUP BY planning.product_uom", \
(date_start, date_stop, val.product_id.id, val.company_id.id,))
planning_qtys = cr.fetchall()
- res = self._to_planning_uom(cr, uid, val, planning_qtys, context)
+ res = self._to_default_uom(cr, uid, val, planning_qtys, context)
return res
- def _to_planning_uom(self, cr, uid, val, qtys, context=None):
+ def _to_default_uom(self, cr, uid, val, qtys, context=None):
+ res_qty = 0
+ if qtys:
+ for qty, prod_uom in qtys:
+ coef = self._to_default_uom_factor(cr, uid, val.product_id.id, prod_uom, context=context)
+ res_qty += qty * coef
+ return res_qty
+
+ def _to_form_uom(self, cr, uid, val, qtys, context=None):
res_qty = 0
if qtys:
- uom_obj = self.pool.get('product.uom')
for qty, prod_uom in qtys:
coef = self._to_default_uom_factor(cr, uid, val.product_id.id, prod_uom, context=context)
res_coef, round_value = self._from_default_uom_factor(cr, uid, val.product_id.id, val.product_uom.id, context=context)
@@ -369,7 +370,7 @@ class stock_planning(osv.osv):
'GROUP BY product_uom', \
(val.product_id.id,val.period_id.id, val.company_id.id))
company_qtys = cr.fetchall()
- res[val.id]['company_forecast'] = self._to_planning_uom(cr, uid, val, company_qtys, context)
+ res[val.id]['company_forecast'] = self._to_form_uom(cr, uid, val, company_qtys, context)
cr.execute('SELECT sum(product_qty), product_uom \
FROM stock_sale_forecast \
@@ -377,8 +378,8 @@ class stock_planning(osv.osv):
'GROUP BY product_uom', \
(val.product_id.id,val.period_id.id, val.warehouse_id.id))
warehouse_qtys = cr.fetchall()
- res[val.id]['warehouse_forecast'] = self._to_planning_uom(cr, uid, val, warehouse_qtys, context)
- res[val.id]['warehouse_forecast'] = rounding(res[val.id]['warehouse_forecast'], val.product_id.uom_id.rounding)
+ res[val.id]['warehouse_forecast'] = self._to_form_uom(cr, uid, val, warehouse_qtys, context)
+# res[val.id]['warehouse_forecast'] = rounding(res[val.id]['warehouse_forecast'], val.product_id.uom_id.rounding)
return res
def _get_stock_start(self, cr, uid, val, date, context=None):
@@ -419,7 +420,7 @@ class stock_planning(osv.osv):
res_coef, round_value = self._from_default_uom_factor(cr, uid, val.product_id.id, val.product_uom.id, context=context)
coef = coef * res_coef
res[val.id]['minimum_op'] = rounding(ret[0]*coef, round_value)
- res[val.id]['maximum_op'] = ret[1]*coef
+ res[val.id]['maximum_op'] = rounding(ret[1]*coef, round_value)
return res
def onchange_company(self, cr, uid, ids, company_id=False):
@@ -451,11 +452,11 @@ class stock_planning(osv.osv):
'warehouse_id': fields.many2one('stock.warehouse','Warehouse', required=True),
'product_id': fields.many2one('product.product' , 'Product', required=True, help = 'Product which this planning is created for.'),
'product_uom_categ' : fields.many2one('product.uom.categ', 'Product UoM Category'), # Invisible field for product_uom domain
- 'product_uom': fields.many2one('product.uom', 'UoM', required=True, help = "Unit of Measure used to show the quanities of stock calculation." \
- "You can use units form default category or from second category (UoS category)."),
+ 'product_uom': fields.many2one('product.uom', 'UoM', required=True, help = "Unit of Measure used to show the quantities of stock calculation." \
+ "You can use units from default category or from second category (UoS category)."),
'product_uos_categ': fields.many2one('product.uom.categ', 'Product UoM Category'), # Invisible field for product_uos domain
-# Field used in onchange_uom to check what uom was before change to recalculate quantities acording to old uom (active_uom) and new uom.
- 'active_uom': fields.many2one('product.uom', string = "Active UoM"),
+# Field used in onchange_uom to check what uom was before change to recalculate quantities according to old uom (active_uom) and new uom.
+ 'active_uom': fields.many2one('product.uom', string = "Active UoM"), # It works only in Forecast
'planned_outgoing': fields.float('Planned Out', required=True, \
help = 'Enter planned outgoing quantity from selected Warehouse during the selected Period of selected Product. '\
'To plan this value look at Confirmed Out or Sales Forecasts. This value should be equal or greater than Confirmed Out.'),
@@ -475,16 +476,16 @@ class stock_planning(osv.osv):
help = 'Quantity left to Planned incoming quantity. This is calculated difference between Planned In and Confirmed In. ' \
'For current period Already In is also calculated. This value is used to create procurement for lacking quantity.'),
'outgoing_left': fields.float('Expected Out', readonly=True, \
- help = 'Quantity expected to go out in selected period. As a difference between Planned Out and Confirmed Out. ' \
+ help = 'Quantity expected to go out in selected period besides Confirmed Out. As a difference between Planned Out and Confirmed Out. ' \
'For current period Already Out is also calculated'),
'to_procure': fields.float(string='Planned In', required=True, \
help = 'Enter quantity which (by your plan) should come in. Change this value and observe Stock simulation. ' \
'This value should be equal or greater than Confirmed In.'),
- 'line_time': fields.function(_get_past_future,type='char', string='Past/Future'),
+ 'line_time': fields.function(_get_past_future, type='char', string='Past/Future'),
'minimum_op': fields.function(_get_op, type='float', string = 'Minimum Rule', multi= 'minimum', \
- help = 'Minimum quantity set in Minimum Stock Rules for this Warhouse'),
+ help = 'Minimum quantity set in Minimum Stock Rules for this Warehouse'),
'maximum_op': fields.function(_get_op, type='float', string = 'Maximum Rule', multi= 'maximum', \
- help = 'Maximum quantity set in Minimum Stock Rules for this Warhouse'),
+ help = 'Maximum quantity set in Minimum Stock Rules for this Warehouse'),
'outgoing_before': fields.float('Planned Out Before', readonly=True, \
help= 'Planned Out in periods before calculated. '\
'Between start date of current period and one day before start of calculated period.'),
@@ -499,18 +500,18 @@ class stock_planning(osv.osv):
help= 'Quantity which is already picked up to this warehouse in current period.'),
'stock_only': fields.boolean("Stock Location Only", help = "Check to calculate stock location of selected warehouse only. " \
"If not selected calculation is made for input, stock and output location of warehouse."),
- "procure_to_stock": fields.boolean("Procure To Stock Location", help = "Chect to make procurement to stock location of selected warehouse. " \
+ "procure_to_stock": fields.boolean("Procure To Stock Location", help = "Check to make procurement to stock location of selected warehouse. " \
"If not selected procurement will be made into input location of warehouse."),
"confirmed_forecasts_only": fields.boolean("Validated Forecasts", help = "Check to take validated forecasts only. " \
"If not checked system takes validated and draft forecasts."),
- 'supply_warehouse_id': fields.many2one('stock.warehouse','Source Warehouse', help = "Warehouse used as source in supply pick move created by 'Supply from Another Warhouse'."),
+ 'supply_warehouse_id': fields.many2one('stock.warehouse','Source Warehouse', help = "Warehouse used as source in supply pick move created by 'Supply from Another Warehouse'."),
"stock_supply_location": fields.boolean("Stock Supply Location", help = "Check to supply from Stock location of Supply Warehouse. " \
- "If not checked supply will be made from Output location of Supply Warehouse. Used in 'Supply from Another Warhouse' with Supply Warehouse."),
+ "If not checked supply will be made from Output location of Supply Warehouse. Used in 'Supply from Another Warehouse' with Supply Warehouse."),
}
_defaults = {
- 'state': lambda *args: 'draft' ,
+ 'state': 'draft' ,
'to_procure': 0.0,
'planned_outgoing': 0.0,
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.planning', context=c),
@@ -525,7 +526,7 @@ class stock_planning(osv.osv):
uom = uom_obj.browse(cr, uid, uom_id, context=context)
coef = uom.factor
if uom.category_id.id != product.uom_id.category_id.id:
- coef = coef / product.uos_coeff
+ coef = coef * product.uos_coeff
return product.uom_id.factor / coef
@@ -536,19 +537,22 @@ class stock_planning(osv.osv):
uom = uom_obj.browse(cr, uid, uom_id, context=context)
res = uom.factor
if uom.category_id.id != product.uom_id.category_id.id:
- res = res / product.uos_coeff
+ res = res * product.uos_coeff
return res / product.uom_id.factor, uom.rounding
def calculate_planning(self, cr, uid, ids, context, *args):
- one_minute = relativedelta(minutes=1)
- current_date_beginning_c = datetime.today()
- current_date_end_c = current_date_beginning_c + relativedelta(days=1, minutes=-1) # to get hour 23:59:00
+ one_second = relativedelta(seconds=1)
+ today = datetime.today()
+ current_date_beginning_c = datetime(today.year, today.month, today.day)
+ current_date_end_c = current_date_beginning_c + relativedelta(days=1, seconds=-1) # to get hour 23:59:59
current_date_beginning = current_date_beginning_c.strftime('%Y-%m-%d %H:%M:%S')
current_date_end = current_date_end_c.strftime('%Y-%m-%d %H:%M:%S')
+ _logger.debug("Calculate Planning: current date beg: %s and end: %s", current_date_beginning, current_date_end)
for val in self.browse(cr, uid, ids, context=context):
day = datetime.strptime(val.period_id.date_start, '%Y-%m-%d %H:%M:%S')
- dbefore = datetime(day.year, day.month, day.day) - one_minute
+ dbefore = datetime(day.year, day.month, day.day) - one_second
day_before_calculated_period = dbefore.strftime('%Y-%m-%d %H:%M:%S') # one day before start of calculated period
+ _logger.debug("Day before calculated period: %s ", day_before_calculated_period)
cr.execute("SELECT date_start \
FROM stock_period AS period \
LEFT JOIN stock_planning AS planning \
@@ -558,10 +562,10 @@ class stock_planning(osv.osv):
date = cr.fetchone()
start_date_current_period = date and date[0] or False
start_date_current_period = start_date_current_period or current_date_beginning
-
day = datetime.strptime(start_date_current_period, '%Y-%m-%d %H:%M:%S')
- dbefore = datetime(day.year, day.month, day.day) - one_minute
+ dbefore = datetime(day.year, day.month, day.day) - one_second
date_for_start = dbefore.strftime('%Y-%m-%d %H:%M:%S') # one day before current period
+ _logger.debug("Date for start: %s", date_for_start)
already_out = self._get_in_out(cr, uid, val, start_date_current_period, current_date_end, direction='out', done=True, context=context),
already_in = self._get_in_out(cr, uid, val, start_date_current_period, current_date_end, direction='in', done=True, context=context),
outgoing = self._get_in_out(cr, uid, val, val.period_id.date_start, val.period_id.date_stop, direction='out', done=False, context=context),
@@ -623,7 +627,7 @@ class stock_planning(osv.osv):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
proc_id = self.pool.get('procurement.order').create(cr, uid, {
'company_id' : obj.company_id.id,
- 'name': _('Manual planning for %s') %(obj.period_id.name),
+ 'name': _('MPS planning for %s') %(obj.period_id.name),
'origin': _('MPS(%s) %s') %(user.login, obj.period_id.name),
'date_planned': obj.period_id.date_start,
'product_id': obj.product_id.id,
@@ -633,7 +637,7 @@ class stock_planning(osv.osv):
'product_uos': uos,
'location_id': obj.procure_to_stock and obj.warehouse_id.lot_stock_id.id or obj.warehouse_id.lot_input_id.id,
'procure_method': 'make_to_order',
- 'note' : _(' Procurement created in MPS by user: %s Creation Date: %s \
+ 'note' : _(' Procurement created by MPS for user: %s Creation Date: %s \
\n For period: %s \
\n according to state: \
\n Warehouse Forecast: %s \
@@ -653,7 +657,7 @@ class stock_planning(osv.osv):
self.calculate_planning(cr, uid, ids, context)
prev_text = obj.history or ""
self.write(cr, uid, ids, {
- 'history': _('%s Requisition (%s, %s) %s %s \n') % (prev_text, user.login, time.strftime('%Y.%m.%d %H:%M'),
+ 'history': _('%s Procurement (%s, %s) %s %s \n') % (prev_text, user.login, time.strftime('%Y.%m.%d %H:%M'),
obj.incoming_left, obj.product_uom.name)
})
diff --git a/addons/stock_planning/stock_planning_view.xml b/addons/stock_planning/stock_planning_view.xml
index 9995752cb03..3cbee4d53d8 100644
--- a/addons/stock_planning/stock_planning_view.xml
+++ b/addons/stock_planning/stock_planning_view.xml
@@ -2,8 +2,8 @@
+ name="Stock Periods"
+ parent="stock.menu_stock_configuration" sequence="20"/>
@@ -12,7 +12,7 @@
stock.periodform
-
+
@@ -46,7 +46,7 @@
stock.periodsearch
-
+
@@ -58,11 +58,13 @@
- Stock and Sales Periods
+ Stock Periodsstock.periodformtree,form
+ Stock periods are used for stock planning. Stock periods are independent of account periods. You can use wizard for creating periods and review them here.
+
-
+
@@ -190,14 +192,14 @@
search
-
+
-
+
@@ -215,7 +217,7 @@
formtree,form,graph{"search_default_create_uid":uid}
-
+ This quantity sales forecast is an indication for Stock Planner to make procurement manually or to complement automatic procurement. You can use manual procurement with this forecast when some periods are exceptional for usual minimum stock rules.
-
+ stock.planning.form
@@ -247,6 +249,7 @@
+
@@ -283,7 +286,7 @@
-
+
@@ -295,13 +298,13 @@
search
-
+
-
+
@@ -338,12 +341,14 @@
ir.actions.act_windowformtree,form
-
+ The Master Procurement Schedule can be the main driver for warehouse replenishment, or can complement the automatic MRP scheduling (minimum stock rules, etc.).
+Each MPS line gives you a pre-computed overview of the incoming and outgoing quantities of a given product for a given Stock Period in a given Warehouse, based on the current and future stock levels,
+as well as the planned stock moves. The forecast quantities can be altered manually, and when satisfied with resulting (simulated) Stock quantity, you can trigger the procurement of what is missing to reach your desired quantities
diff --git a/addons/stock_planning/test/stock_planning.yml b/addons/stock_planning/test/stock_planning.yml
index eb0ccbd20ef..77f1d5a427d 100644
--- a/addons/stock_planning/test/stock_planning.yml
+++ b/addons/stock_planning/test/stock_planning.yml
@@ -96,7 +96,7 @@
!python {model: stock.sale.forecast}: |
forecast_ids = self.search(cr, uid, [('product_id','=',ref('product.product_product_pc1'))])
write_dict = {'product_qty': 30.0,
- 'analyzed_dept_id': ref("hr.dep_sales"),
+ 'analyzed_section_id': ref("crm.section_sales_department"),
'analyzed_period1_id': ref("stock_period_03"),
'analyzed_user_id': ref("base.user_root"),
'analyzed_warehouse_id': ref("stock.warehouse0")}
diff --git a/addons/stock_planning/wizard/stock_planning_create_periods.py b/addons/stock_planning/wizard/stock_planning_create_periods.py
index 7d4012453de..05f8aa925a9 100644
--- a/addons/stock_planning/wizard/stock_planning_create_periods.py
+++ b/addons/stock_planning/wizard/stock_planning_create_periods.py
@@ -52,8 +52,6 @@ class stock_period_createlines(osv.osv_memory):
}
def create_stock_periods(self, cr, uid, ids, context=None):
- if context is None:
- context = {}
interval = context.get('interval',0)
name = context.get('name','Daily')
period_obj = self.pool.get('stock.period')
@@ -106,7 +104,7 @@ class stock_period_createlines(osv.osv_memory):
return {
'domain': "[('id','in', ["+','.join(map(str, lines))+"])]",
'view_type': 'form',
- "view_mode": 'tree, form',
+ "view_mode": 'tree,form',
'res_model': 'stock.period',
'type': 'ir.actions.act_window',
}
diff --git a/addons/stock_planning/wizard/stock_planning_create_periods_view.xml b/addons/stock_planning/wizard/stock_planning_create_periods_view.xml
index 35b55b8f4e7..42730dc8ace 100644
--- a/addons/stock_planning/wizard/stock_planning_create_periods_view.xml
+++ b/addons/stock_planning/wizard/stock_planning_create_periods_view.xml
@@ -8,8 +8,7 @@
stock.period.createlinesform
-
-
+
@@ -24,16 +23,17 @@
- Stock and Sales Planning Periods
+ Stock Periodsstock.period.createlinesformform
+ This wizard helps with the creation of stock planning periods. These periods are independent of financial periods. If you need periods other than day-, week- or month-based, you may also add then manually.new
diff --git a/addons/stock_planning/wizard/stock_planning_createlines.py b/addons/stock_planning/wizard/stock_planning_createlines.py
index 7abb15b6e7d..a6e12ad4bc7 100644
--- a/addons/stock_planning/wizard/stock_planning_createlines.py
+++ b/addons/stock_planning/wizard/stock_planning_createlines.py
@@ -127,16 +127,13 @@ class stock_planning_createlines(osv.osv_memory):
'stock_supply_location': stock_supply_location,
}))
- result = mod_obj._get_id(cr, uid, 'stock_planning', 'view_stock_planning_filter')
- id = mod_obj.read(cr, uid, result, ['res_id'], context=context)
return {
'domain': "[('id','in', ["+','.join(map(str, planning_lines))+"])]",
'view_type': 'form',
- "view_mode": 'tree, form',
+ "view_mode": 'tree,form',
'res_model': 'stock.planning',
'type': 'ir.actions.act_window',
- 'search_view_id': id['res_id'],
}
stock_planning_createlines()
diff --git a/addons/stock_planning/wizard/stock_planning_createlines_view.xml b/addons/stock_planning/wizard/stock_planning_createlines_view.xml
index 7dc4504daef..b0da05b24ab 100644
--- a/addons/stock_planning/wizard/stock_planning_createlines_view.xml
+++ b/addons/stock_planning/wizard/stock_planning_createlines_view.xml
@@ -1,24 +1,18 @@
-
-
stock.planning.createlines.formstock.planning.createlinesform
-
+
-
-
-
-
@@ -30,15 +24,17 @@
Create Stock Planning Linesstock.planning.createlines
+ stock.planningformform
+ This wizard helps create MPS planning lines for a given selected period and warehouse, so you don't have to create them one by one. The wizard doesn't duplicate lines if they already exist for this selection.new
-
+
-
-
-
-
@@ -34,12 +29,10 @@
formnew
+ This wizard helps create many forecast lines at once. After creating them you only have to fill in the forecast quantities. The wizard doesn't duplicate the line when another one exist for the same selection.
-
-
-
diff --git a/addons/subscription/subscription.py b/addons/subscription/subscription.py
index 4f40ea25c35..0f2630ffbb5 100644
--- a/addons/subscription/subscription.py
+++ b/addons/subscription/subscription.py
@@ -136,6 +136,12 @@ class subscription_subscription(osv.osv):
self.write(cr, uid, [row['id']], {'state':state})
return True
+ def unlink(self, cr, uid, ids, context=None):
+ for record in self.browse(cr, uid, ids, context or {}):
+ if record.state=="running":
+ raise osv.except_osv(_('Error !'),_('You cannot delete an active subscription !'))
+ return super(subscription_subscription, self).unlink(cr, uid, ids, context)
+
def set_done(self, cr, uid, ids, context=None):
res = self.read(cr,uid, ids, ['cron_id'])
ids2 = [x['cron_id'][0] for x in res if x['id']]
diff --git a/addons/subscription/subscription_view.xml b/addons/subscription/subscription_view.xml
index b3c39962190..550240d522e 100644
--- a/addons/subscription/subscription_view.xml
+++ b/addons/subscription/subscription_view.xml
@@ -75,7 +75,7 @@
search
-
+
@@ -85,11 +85,11 @@
domain="[('date_init','<', time.strftime('%%Y-%%m-%%d 23:59:59')), ('date_init','>=', time.strftime('%%Y-%%m-%%d 00:00:00'))]"/>
-
+
-
+
@@ -169,7 +169,7 @@
search
-
+
diff --git a/addons/survey/__openerp__.py b/addons/survey/__openerp__.py
index e44efbe0bf9..5a551c28a3d 100644
--- a/addons/survey/__openerp__.py
+++ b/addons/survey/__openerp__.py
@@ -33,7 +33,7 @@ Different users may give different answers of question and according to that sur
Partners are also sent mails with user name and password for the invitation of the survey
""",
'author': 'OpenERP SA',
- 'depends': ['base_tools'],
+ 'depends': ['base_tools', 'mail'],
'update_xml': ['survey_report.xml',
'survey_data.xml',
'wizard/survey_selection.xml',
diff --git a/addons/survey/survey.py b/addons/survey/survey.py
index bd27f313d82..78b824af004 100644
--- a/addons/survey/survey.py
+++ b/addons/survey/survey.py
@@ -92,7 +92,7 @@ class survey(osv.osv):
def survey_cancel(self, cr, uid, ids, arg):
self.write(cr, uid, ids, {'state': 'cancel' })
return True
-
+
def copy(self, cr, uid, ids, default=None, context=None):
vals = {}
current_rec = self.read(cr, uid, ids, context=context)
@@ -181,9 +181,7 @@ class survey_page(osv.osv):
if context is None:
context = {}
data = super(survey_page, self).default_get(cr, uid, fields, context)
- if context.get('line_order',False):
- if len(context['line_order'][-1]) > 2 and type(context['line_order'][-1][2]) == type({}) and context['line_order'][-1][2].has_key('sequence'):
- data['sequence'] = context['line_order'][-1][2]['sequence'] + 1
+ self.pool.get('survey.question').data_get(cr,uid,data,context)
if context.has_key('survey_id'):
data['survey_id'] = context.get('survey_id', False)
return data
@@ -504,14 +502,21 @@ class survey_question(osv.osv):
'context': context
}
+ def data_get(self, cr, uid, data, context):
+ if data and context:
+ if context.get('line_order', False):
+ lines = context.get('line_order')
+ seq = data.get('sequence', 0)
+ for line in lines:
+ seq = seq + 1
+ data.update({'sequence': seq})
+ return data
+
def default_get(self, cr, uid, fields, context=None):
if context is None:
context = {}
data = super(survey_question, self).default_get(cr, uid, fields, context)
- if context.get('line_order',False):
- if len(context['line_order'][-1]) > 2 and type(context['line_order'][-1][2]) == type({}) and context['line_order'][-1][2].has_key('sequence'):
- data['sequence'] = context['line_order'][-1][2]['sequence'] + 1
-
+ self.data_get(cr,uid,data,context)
if context.has_key('page_id'):
data['page_id']= context.get('page_id', False)
return data
@@ -604,9 +609,7 @@ class survey_answer(osv.osv):
if context is None:
context = {}
data = super(survey_answer, self).default_get(cr, uid, fields, context)
- if context.get('line_order', False):
- if len(context['line_order'][-1]) > 2 and type(context['line_order'][-1][2]) == type({}) and context['line_order'][-1][2].has_key('sequence'):
- data['sequence'] = context['line_order'][-1][2]['sequence'] + 1
+ self.pool.get('survey.question').data_get(cr,uid,data,context)
return data
survey_answer()
diff --git a/addons/survey/survey_view.xml b/addons/survey/survey_view.xml
index fae6c89210a..00ac7620be7 100644
--- a/addons/survey/survey_view.xml
+++ b/addons/survey/survey_view.xml
@@ -260,17 +260,17 @@
search
-
+
-
-
-
+
+
+
-
+
@@ -476,7 +476,7 @@
-
+
@@ -683,7 +683,7 @@
-
+
@@ -1095,18 +1095,18 @@
search
-
+
-
-
-
+
+
+
-
+
diff --git a/addons/survey/wizard/survey_answer.py b/addons/survey/wizard/survey_answer.py
index 896bde70d3c..471290af2cb 100644
--- a/addons/survey/wizard/survey_answer.py
+++ b/addons/survey/wizard/survey_answer.py
@@ -49,7 +49,7 @@ class survey_question_wiz(osv.osv_memory):
@param context: A standard dictionary for contextual values
@return : Dictionary value for created view of particular survey pages.
"""
-
+
result = super(survey_question_wiz, self).fields_view_get(cr, uid, view_id, \
view_type, context, toolbar,submenu)
@@ -61,6 +61,7 @@ class survey_question_wiz(osv.osv_memory):
sur_response_obj = self.pool.get('survey.response')
que_col_head = self.pool.get('survey.question.column.heading')
user_obj = self.pool.get('res.users')
+ mail_message = self.pool.get('mail.message')
if context is None:
context = {}
if view_type in ['form']:
@@ -79,7 +80,7 @@ class survey_question_wiz(osv.osv_memory):
wiz_id = surv_name_wiz.create(cr, uid, res_data)
sur_name_rec = surv_name_wiz.browse(cr, uid, wiz_id, context=context)
context.update({'sur_name_id' :wiz_id})
-
+
if context.has_key('active_id'):
context.pop('active_id')
@@ -401,7 +402,7 @@ class survey_question_wiz(osv.osv_memory):
response_id = surv_name_wiz.read(cr, uid, context.get('sur_name_id',False))['response']
context.update({'response_id':response_id})
report = self.create_report(cr, uid, [int(survey_id)], 'report.survey.browse.response', survey_data.title,context)
- attachments = []
+ attachments = {}
file = open(addons.get_module_resource('survey', 'report') + survey_data.title + ".pdf")
file_data = ""
while 1:
@@ -410,7 +411,7 @@ class survey_question_wiz(osv.osv_memory):
if not line:
break
- attachments.append((survey_data.title + ".pdf",file_data))
+ attachments[survey_data.title + ".pdf"] = file_data
file.close()
os.remove(addons.get_module_resource('survey', 'report') + survey_data.title + ".pdf")
user_email = False
@@ -428,7 +429,7 @@ class survey_question_wiz(osv.osv_memory):
if user_email and resp_email:
user_name = user_obj.browse(cr, uid, uid, context=context).name
mail = "Hello " + survey_data.responsible_id.name + ",\n\n " + str(user_name) + " Give Response Of " + survey_data.title + " Survey.\n\n Thanks,"
- tools.email_send(user_email, [resp_email], "Survey Answer Of " + str(user_name) , mail, attach = attachments)
+ mail_message.schedule_with_attach(cr, uid, user_email, [resp_email], "Survey Answer Of " + str(user_name) , mail, attachments=attachments, context=context)
xml_form = etree.Element('form', {'string': _('Complete Survey Answer')})
etree.SubElement(xml_form, 'separator', {'string': 'Complete Survey', 'colspan': "4"})
@@ -447,7 +448,7 @@ class survey_question_wiz(osv.osv_memory):
@param self: The object pointer
@param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
+ @param uid: the current user’s ID for security checks,
@param res_ids: List of survey answer IDs,
@param report_name: name of the report,
@param file_name: To give file name of the report,
diff --git a/addons/survey/wizard/survey_answer.xml b/addons/survey/wizard/survey_answer.xml
index db2eb1073d9..b5485bf6370 100644
--- a/addons/survey/wizard/survey_answer.xml
+++ b/addons/survey/wizard/survey_answer.xml
@@ -23,7 +23,7 @@
search
-
+
diff --git a/addons/survey/wizard/survey_selection.py b/addons/survey/wizard/survey_selection.py
index 8d613c74bd0..c6eebd62a67 100644
--- a/addons/survey/wizard/survey_selection.py
+++ b/addons/survey/wizard/survey_selection.py
@@ -64,7 +64,7 @@ class survey_name_wiz(osv.osv_memory):
survey_user_group_id = self.pool.get('res.groups').search(cr, uid, [('name', '=', 'Survey / User')])
group_id = self.pool.get('res.groups').search(cr, uid, [('name', 'in', ('Tools / Manager','Tools / User','Survey / User'))])
user_obj = self.pool.get('res.users')
- user_rec = user_obj.read(cr, uid, uid)
+ user_rec = user_obj.read(cr, uid, uid, [])
if survey_user_group_id:
if survey_user_group_id == user_rec['groups_id']:
for sur in surv_obj.browse(cr, uid, surv_obj.search(cr, uid, [])):
diff --git a/addons/survey/wizard/survey_send_invitation.py b/addons/survey/wizard/survey_send_invitation.py
index 49cbac82b46..e57c8f91d8b 100644
--- a/addons/survey/wizard/survey_send_invitation.py
+++ b/addons/survey/wizard/survey_send_invitation.py
@@ -102,6 +102,7 @@ class survey_send_invitation(osv.osv_memory):
partner_ids = record['partner_ids']
user_ref= self.pool.get('res.users')
survey_ref= self.pool.get('survey')
+ mail_message = self.pool.get('mail.message')
model_data_obj = self.pool.get('ir.model.data')
group_id = model_data_obj._get_id(cr, uid, 'base', 'group_survey_user')
@@ -118,7 +119,7 @@ class survey_send_invitation(osv.osv_memory):
res_user = ""
user_exists = False
new_user = []
- attachments = []
+ attachments = {}
current_sur = survey_ref.browse(cr, uid, context.get('active_id'), context=context)
exist_user = current_sur.invited_user_ids
if exist_user:
@@ -133,8 +134,8 @@ class survey_send_invitation(osv.osv_memory):
file_data += line
if not line:
break
- attachments.append((id.title +".pdf",file_data))
file.close()
+ attachments[id.title +".pdf"] = file_data
os.remove(addons.get_module_resource('survey', 'report') + id.title +".pdf")
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids):
@@ -151,8 +152,8 @@ class survey_send_invitation(osv.osv_memory):
mail = record['mail']%{'login':addr.email, 'passwd':user.password, \
'name' : addr.name}
if record['send_mail_existing']:
- tools.email_send(record['mail_from'], [addr.email] , \
- record['mail_subject_existing'] , mail)
+ mail_message.schedule_with_attach(cr, uid, record['mail_from'], [addr.email] , \
+ record['mail_subject_existing'] , mail, context=context)
existing+= "- %s (Login: %s, Password: %s)\n" % (user.name, addr.email, \
user.password)
continue
@@ -162,8 +163,8 @@ class survey_send_invitation(osv.osv_memory):
mail = record['mail']%{'login': user_email.login, \
'passwd': user_email.password, 'name': addr.name}
if record['send_mail_existing']:
- tools.email_send(record['mail_from'], [addr.email],\
- record['mail_subject_existing'], mail)
+ mail_message.schedule_with_attach(cr, uid, record['mail_from'], [addr.email],\
+ record['mail_subject_existing'], mail, context=context)
res_user+= "- %s (Login: %s, Password: %s)\n" % \
(user_email.name, user_email.login, user_email.password)
continue
@@ -175,8 +176,8 @@ class survey_send_invitation(osv.osv_memory):
out+= addr.email + ',' + passwd + '\n'
mail= record['mail'] % {'login' : addr.email, 'passwd' : passwd, 'name' : addr.name}
if record['send_mail']:
- ans = tools.email_send(record['mail_from'], [addr.email], \
- record['mail_subject'], mail,attach = attachments)
+ ans = mail_message.schedule_with_attach(cr, uid, record['mail_from'], [addr.email], \
+ record['mail_subject'], mail, attachments=attachments, context=context)
if ans:
res_data = {'name': addr.name or 'Unknown',
diff --git a/addons/thunderbird/__openerp__.py b/addons/thunderbird/__openerp__.py
index 1729cc430cf..51e4153e897 100644
--- a/addons/thunderbird/__openerp__.py
+++ b/addons/thunderbird/__openerp__.py
@@ -24,7 +24,7 @@
"version" : "1.0",
"author" : ['OpenERP SA', 'Axelor'],
"website" : "http://www.openerp.com/",
- "depends" : ["base","mail_gateway"],
+ "depends" : ["base","mail"],
"category" : "Tools",
"description": """
This module is required for the Thuderbird Plug-in to work properly.
@@ -35,7 +35,6 @@ OpenERP objects. You can select a partner, a task, a project, an analytical
account, or any other object and attach the selected mail as a .eml file in
the attachment of a selected record. You can create documents for CRM Lead,
HR Applicant and Project Issue from selected mails.
-
""",
"init_xml" : [],
"demo_xml" : [],
diff --git a/addons/thunderbird/partner/partner.py b/addons/thunderbird/partner/partner.py
index c506cf58b4f..80a4bfa4d98 100644
--- a/addons/thunderbird/partner/partner.py
+++ b/addons/thunderbird/partner/partner.py
@@ -23,113 +23,7 @@ from osv import osv
import base64
import email
import tools
-import binascii
-import dateutil.parser
-class email_server_tools(osv.osv_memory):
- _inherit = "email.server.tools"
- def history_message(self, cr, uid, model, res_id, message, context=None):
- #@param message: string of mail which is read from EML File
- attachment_pool = self.pool.get('ir.attachment')
- msg = self.parse_message(message)
- attachments = msg.get('attachments', [])
- att_ids = []
- for attachment in attachments:
- data_attach = {
- 'name': attachment,
- 'datas': binascii.b2a_base64(str(attachments.get(attachment))),
- 'datas_fname': attachment,
- 'description': 'Mail attachment From Thunderbird msg_id: %s' %(msg.get('message_id', '')),
- 'res_model': model,
- 'res_id': res_id,
- }
- att_ids.append(attachment_pool.create(cr, uid, data_attach))
- return self.history(cr, uid, model, res_id, msg, att_ids)
-
- def parse_message(self, message):
- #TOCHECK: put this function in mailgateway module
- if isinstance(message, unicode):
- message = message.encode('utf-8')
- msg_txt = email.message_from_string(message)
- message_id = msg_txt.get('message-id', False)
- msg = {}
- fields = msg_txt.keys()
- msg['id'] = message_id
- msg['message-id'] = message_id
- if 'Subject' in fields:
- msg['subject'] = self._decode_header(msg_txt.get('Subject'))
-
- if 'Content-Type' in fields:
- msg['content-type'] = msg_txt.get('Content-Type')
-
- if 'From' in fields:
- msg['from'] = self._decode_header(msg_txt.get('From'))
-
- if 'Delivered-To' in fields:
- msg['to'] = self._decode_header(msg_txt.get('Delivered-To'))
-
- if 'CC' in fields:
- msg['cc'] = self._decode_header(msg_txt.get('CC'))
-
- if 'Reply-to' in fields:
- msg['reply'] = self._decode_header(msg_txt.get('Reply-To'))
-
- if 'Date' in fields:
- date = self._decode_header(msg_txt.get('Date'))
- msg['date'] = dateutil.parser.parse(date).strftime("%Y-%m-%d %H:%M:%S")
-
- if 'Content-Transfer-Encoding' in fields:
- msg['encoding'] = msg_txt.get('Content-Transfer-Encoding')
-
- if 'References' in fields:
- msg['references'] = msg_txt.get('References')
-
- if 'In-Reply-To' in fields:
- msg['in-reply-to'] = msg_txt.get('In-Reply-To')
-
- if 'X-Priority' in fields:
- msg['priority'] = msg_txt.get('X-Priority', '3 (Normal)').split(' ')[0]
-
- if not msg_txt.is_multipart() or 'text/plain' in msg.get('Content-Type', ''):
- encoding = msg_txt.get_content_charset()
- body = msg_txt.get_payload(decode=True)
- msg['body'] = tools.ustr(body, encoding)
-
- attachments = {}
- has_plain_text = False
- if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', ''):
- body = ""
- for part in msg_txt.walk():
- if part.get_content_maintype() == 'multipart':
- continue
-
- encoding = part.get_content_charset()
- filename = part.get_filename()
- if part.get_content_maintype()=='text':
- content = part.get_payload(decode=True)
- if filename:
- attachments[filename] = content
- elif not has_plain_text:
- # main content parts should have 'text' maintype
- # and no filename. we ignore the html part if
- # there is already a plaintext part without filename,
- # because presumably these are alternatives.
- content = tools.ustr(content, encoding)
- if part.get_content_subtype() == 'html':
- body = tools.ustr(tools.html2plaintext(content))
- elif part.get_content_subtype() == 'plain':
- body = content
- has_plain_text = True
- elif part.get_content_maintype() in ('application', 'image'):
- if filename :
- attachments[filename] = part.get_payload(decode=True)
- else:
- res = part.get_payload(decode=True)
- body += tools.ustr(res, encoding)
-
- msg['body'] = body
- msg['attachments'] = attachments
- return msg
-email_server_tools()
+from tools.translate import _
class thunderbird_partner(osv.osv_memory):
_name = "thunderbird.partner"
@@ -151,21 +45,21 @@ class thunderbird_partner(osv.osv_memory):
ref_ids = str(dictcreate.get('ref_ids')).split(';')
msg = dictcreate.get('message')
mail = msg
- msg = self.pool.get('email.server.tools').parse_message(msg)
- server_tools_pool = self.pool.get('email.server.tools')
+ mail_message = self.pool.get('mail.message')
+ msg = mail_message.parse_message(msg)
+ subject = msg.get('Subject', False)
+ thread_pool = self.pool.get('mail.thread')
message_id = msg.get('message-id', False)
- msg_pool = self.pool.get('mailgate.message')
msg_ids = []
res = {}
res_ids = []
obj_list= ['crm.lead','project.issue','hr.applicant','res.partner']
for ref_id in ref_ids:
- msg_new = dictcreate.get('message')
ref = ref_id.split(',')
model = ref[0]
res_id = int(ref[1])
if message_id:
- msg_ids = msg_pool.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)])
+ msg_ids = mail_message.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)])
if msg_ids and len(msg_ids):
continue
if model not in obj_list:
@@ -193,7 +87,23 @@ class thunderbird_partner(osv.osv_memory):
res['datas'] = base64.b64encode(mail)
res['res_id'] = res_id
obj_attch.create(cr, uid, res)
- server_tools_pool.history_message(cr, uid, model, res_id, msg_new)
+ threads = self.pool.get(model).browse(cr, uid, res_id)
+
+ thread_pool.message_append(cr, uid,
+ [threads],
+ subject = msg.get('subject'),
+ details = msg.get('body_text'),
+ email_to = msg.get('to'),
+ email_from = msg.get('from'),
+ email_cc = msg.get('cc'),
+ message_id = msg.get('message-id'),
+ references = msg.get('references', False) or msg.get('in-reply-to', False),
+ attachments = msg.get('attachments', {}),
+ email_date = msg.get('date'),
+ body_html= msg.get('body_html'),
+ subtype = msg.get('subtype'),
+ headers = msg.get('headers'),
+ reply_to = msg.get('reply'))
res_ids.append(res_id)
return len(res_ids)
@@ -201,7 +111,7 @@ class thunderbird_partner(osv.osv_memory):
dictcreate = dict(vals)
model = str(dictcreate.get('model'))
message = dictcreate.get('message')
- return self.pool.get('email.server.tools').process_email(cr, uid, model, message, attach=True, context=None)
+ return self.pool.get('mail.thread').message_process(cr, uid, model, message)
def search_message(self, cr, uid, message, context=None):
#@param message: string of mail which is read from EML File
@@ -209,26 +119,26 @@ class thunderbird_partner(osv.osv_memory):
references = []
dictcreate = dict(message)
msg = dictcreate.get('message')
- msg = self.pool.get('email.server.tools').parse_message(msg)
+ msg = self.pool.get('mail.message').parse_message(msg)
message_id = msg.get('message-id')
refs = msg.get('references',False)
references = False
if refs:
references = refs.split()
- msg_pool = self.pool.get('mailgate.message')
+ mail_message = self.pool.get('mail.message')
model = ''
res_id = 0
if message_id:
- msg_ids = msg_pool.search(cr, uid, [('message_id','=',message_id)])
+ msg_ids = mail_message.search(cr, uid, [('message_id','=',message_id)])
if msg_ids and len(msg_ids):
- msg = msg_pool.browse(cr, uid, msg_ids[0])
+ msg = mail_message.browse(cr, uid, msg_ids[0])
model = msg.model
res_id = msg.res_id
else:
if references :
- msg_ids = msg_pool.search(cr, uid, [('message_id','in',references)])
+ msg_ids = mail_message.search(cr, uid, [('message_id','in',references)])
if msg_ids and len(msg_ids):
- msg = msg_pool.browse(cr, uid, msg_ids[0])
+ msg = mail_message.browse(cr, uid, msg_ids[0])
model = msg.model
res_id = msg.res_id
return (model,res_id)
diff --git a/addons/wiki/i18n/hr.po b/addons/wiki/i18n/hr.po
index d1bb1e3f456..62023e0099d 100644
--- a/addons/wiki/i18n/hr.po
+++ b/addons/wiki/i18n/hr.po
@@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:16+0000\n"
-"PO-Revision-Date: 2010-10-30 12:31+0000\n"
-"Last-Translator: OpenERP Administrators \n"
+"PO-Revision-Date: 2011-09-20 02:17+0000\n"
+"Last-Translator: wooer \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-09-05 05:38+0000\n"
-"X-Generator: Launchpad (build 13830)\n"
+"X-Launchpad-Export-Date: 2011-09-21 04:50+0000\n"
+"X-Generator: Launchpad (build 13996)\n"
#. module: wiki
#: field:wiki.groups,template:0
@@ -30,7 +30,7 @@ msgstr "Wiki stranice"
#. module: wiki
#: field:wiki.groups,method:0
msgid "Display Method"
-msgstr ""
+msgstr "Način prikaza"
#. module: wiki
#: view:wiki.wiki:0
@@ -57,7 +57,7 @@ msgstr "Odlomak"
#. module: wiki
#: help:wiki.wiki,toc:0
msgid "Indicates that this pages have a table of contents or not"
-msgstr ""
+msgstr "Ukazuje da li ove stranice imaju tablicu sadržaja ili ne"
#. module: wiki
#: model:ir.model,name:wiki.model_wiki_wiki_history
@@ -90,7 +90,7 @@ msgstr ""
#: view:wiki.wiki:0
#: field:wiki.wiki,write_uid:0
msgid "Last Contributor"
-msgstr ""
+msgstr "Zadnji suradnik"
#. module: wiki
#: field:wiki.create.menu,menu_parent_id:0
@@ -117,7 +117,7 @@ msgstr "Naslov"
#. module: wiki
#: model:ir.model,name:wiki.model_wiki_create_menu
msgid "Wizard Create Menu"
-msgstr ""
+msgstr "Čarobnjak za stvaranje izbornika"
#. module: wiki
#: field:wiki.wiki,history_id:0
@@ -127,13 +127,13 @@ msgstr "Redovi povijesti"
#. module: wiki
#: view:wiki.wiki:0
msgid "Page Content"
-msgstr ""
+msgstr "Sadržaj stranice"
#. module: wiki
#: code:addons/wiki/wiki.py:236
#, python-format
msgid "Warning !"
-msgstr ""
+msgstr "Upozorenje !"
#. module: wiki
#: code:addons/wiki/wiki.py:236
@@ -159,7 +159,7 @@ msgstr "Opis"
#. module: wiki
#: field:wiki.wiki,review:0
msgid "Needs Review"
-msgstr ""
+msgstr "Potrebno pregledati"
#. module: wiki
#: help:wiki.wiki,review:0
@@ -167,6 +167,7 @@ msgid ""
"Indicates that this page should be reviewed, raising the attention of other "
"contributors"
msgstr ""
+"Ukazuje da ovu stranicu treba pregledati, skrećući pozornost drugih suradnika"
#. module: wiki
#: view:wiki.create.menu:0
@@ -177,7 +178,7 @@ msgstr "Informacije o izborniku"
#. module: wiki
#: model:ir.actions.act_window,name:wiki.act_wiki_wiki_history
msgid "Page History"
-msgstr ""
+msgstr "Povijest stranice"
#. module: wiki
#: selection:wiki.groups,method:0
diff --git a/addons/wiki/wiki_view.xml b/addons/wiki/wiki_view.xml
index ef801704037..98e44ae669d 100644
--- a/addons/wiki/wiki_view.xml
+++ b/addons/wiki/wiki_view.xml
@@ -163,14 +163,14 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
+