[MERGE]:trunk
bzr revid: apa@tinyerp.com-20121009090057-gtwa0gu95m5s1zcr
This commit is contained in:
commit
23f406063f
|
@ -461,10 +461,9 @@ class account_bank_statement(osv.osv):
|
|||
return {}
|
||||
balance_start = self._compute_balance_end_real(cr, uid, journal_id, context=context)
|
||||
|
||||
journal_data = self.pool.get('account.journal').read(cr, uid, journal_id, ['default_debit_account_id', 'company_id'], context=context)
|
||||
account_id = journal_data['default_debit_account_id']
|
||||
journal_data = self.pool.get('account.journal').read(cr, uid, journal_id, ['company_id'], context=context)
|
||||
company_id = journal_data['company_id']
|
||||
return {'value': {'balance_start': balance_start, 'account_id': account_id, 'company_id': company_id}}
|
||||
return {'value': {'balance_start': balance_start, 'company_id': company_id}}
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
stat = self.read(cr, uid, ids, ['state'], context=context)
|
||||
|
|
|
@ -1360,6 +1360,7 @@ class account_invoice_line(osv.osv):
|
|||
_columns = {
|
||||
'name': fields.text('Description', required=True),
|
||||
'origin': fields.char('Source', size=256, help="Reference of the document that produced this invoice."),
|
||||
'sequence': fields.integer('Sequence', help="Gives the sequence of this line when displaying the invoice."),
|
||||
'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
|
||||
'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
|
||||
'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
|
||||
|
|
|
@ -189,7 +189,7 @@
|
|||
</group>
|
||||
<notebook>
|
||||
<page string="Invoice">
|
||||
<field context="{'partner_id': partner_id, 'price_type': 'price_type' in dir() and price_type or False, 'type': type}" name="invoice_line">
|
||||
<field context="{'partner_id': partner_id, 'price_type': context.get('price_type') or False, 'type': type}" name="invoice_line">
|
||||
<tree string="Invoice lines" editable="bottom">
|
||||
<field name="product_id"
|
||||
on_change="product_id_change(product_id, uos_id, quantity, name, parent.type, parent.partner_id, parent.fiscal_position, price_unit, parent.currency_id, context, parent.company_id)"/>
|
||||
|
|
|
@ -1549,10 +1549,7 @@
|
|||
<field name="view_mode">account_reconciliation_list</field>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
Good job!
|
||||
</p><p>
|
||||
There is nothing to reconcile. All invoices and payments
|
||||
have been reconciled, your partner balance is clean.
|
||||
No journal items found.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -3,10 +3,17 @@
|
|||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="AccountReconciliation">
|
||||
<t t-if="widget.current_partner === null">
|
||||
<div class="oe_view_nocontent">
|
||||
<p>
|
||||
Good job!
|
||||
</p><p>
|
||||
There is nothing to reconcile. All invoices and payments
|
||||
have been reconciled, your partner balance is clean.
|
||||
</p>
|
||||
</div>
|
||||
</t>
|
||||
<div class="oe_account_reconciliation">
|
||||
<t t-if="widget.current_partner === null">
|
||||
There is no pending reconciliation.
|
||||
</t>
|
||||
<t t-if="widget.current_partner !== null">
|
||||
<div>
|
||||
<div>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
-
|
||||
I create a supplier invoice
|
||||
-
|
||||
!record {model: account.invoice, id: account_invoice_supplier0}:
|
||||
!record {model: account.invoice, id: account_invoice_supplier0, view: invoice_supplier_form}:
|
||||
account_id: account.a_pay
|
||||
check_total: 3000.0
|
||||
company_id: base.main_company
|
||||
|
|
|
@ -6,7 +6,12 @@
|
|||
<field name="model">account.invoice.confirm</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Confirm Draft Invoices" version="7.0">
|
||||
<separator string="Confirm Draft Invoices"/>
|
||||
<p class="oe_grey">
|
||||
Once draft invoices are confirmed, you will not be able
|
||||
to modify them. The invoices will receive a unique
|
||||
number and journal items will be created in your chart
|
||||
of accounts.
|
||||
</p>
|
||||
<footer>
|
||||
<button string="Confirm Invoices" name="invoice_confirm" type="object" default_focus="1" class="oe_highlight"/>
|
||||
or
|
||||
|
@ -27,7 +32,6 @@
|
|||
<field name="model">account.invoice.cancel</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Cancel Selected Invoices" version="7.0">
|
||||
<separator string="Cancel Selected Invoices"/>
|
||||
<footer>
|
||||
<button string="Cancel Invoices" name="invoice_cancel" type="object" default_focus="1" class="oe_highlight"/>
|
||||
or
|
||||
|
|
|
@ -7,14 +7,14 @@ msgstr ""
|
|||
"Project-Id-Version: OpenERP Server 5.0.4\n"
|
||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
||||
"POT-Creation-Date: 2012-02-08 00:35+0000\n"
|
||||
"PO-Revision-Date: 2009-02-03 06:24+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"PO-Revision-Date: 2012-10-08 15:59+0000\n"
|
||||
"Last-Translator: waleed bazaza <waleed_bazaza@yahoo.com>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-28 06:07+0000\n"
|
||||
"X-Generator: Launchpad (build 15864)\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-09 04:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16112)\n"
|
||||
|
||||
#. module: account_followup
|
||||
#: view:account_followup.followup:0
|
||||
|
@ -42,7 +42,7 @@ msgstr "متابعة"
|
|||
#: help:account.followup.print.all,test_print:0
|
||||
msgid ""
|
||||
"Check if you want to print followups without changing followups level."
|
||||
msgstr ""
|
||||
msgstr "اختر هذه الخانة إذا أردت طباعة المتابعات بدون تغيير متوى المتابعات."
|
||||
|
||||
#. module: account_followup
|
||||
#: model:account_followup.followup.line,description:account_followup.demo_followup_line2
|
||||
|
@ -101,7 +101,7 @@ msgstr "الدليل"
|
|||
#. module: account_followup
|
||||
#: view:account_followup.stat:0
|
||||
msgid "Follow up Entries with period in current year"
|
||||
msgstr ""
|
||||
msgstr "مدخلات المتابعات بفترات في السنة الحالية"
|
||||
|
||||
#. module: account_followup
|
||||
#: view:account.followup.print.all:0
|
||||
|
@ -314,6 +314,9 @@ msgid ""
|
|||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
"جميع رسائل البريد الالكتروني قد أرسلت بنجاح للشركاء:.\n"
|
||||
"\n"
|
||||
"%s"
|
||||
|
||||
#. module: account_followup
|
||||
#: constraint:account_followup.followup.line:0
|
||||
|
@ -362,6 +365,11 @@ msgid ""
|
|||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"\n"
|
||||
"رسالة البريد الالكتروني قد أرسلت للشركاء التاليين.!\n"
|
||||
"\n"
|
||||
"%s"
|
||||
|
||||
#. module: account_followup
|
||||
#: help:account.followup.print,date:0
|
||||
|
@ -503,7 +511,7 @@ msgstr "تقرير المتابعة"
|
|||
#. module: account_followup
|
||||
#: view:account_followup.followup.line:0
|
||||
msgid "Follow-Up Steps"
|
||||
msgstr ""
|
||||
msgstr "خطوات المتابعة"
|
||||
|
||||
#. module: account_followup
|
||||
#: field:account_followup.stat,period_id:0
|
||||
|
@ -514,7 +522,7 @@ msgstr "فترة"
|
|||
#: code:addons/account_followup/wizard/account_followup_print.py:307
|
||||
#, python-format
|
||||
msgid "Followup Summary"
|
||||
msgstr ""
|
||||
msgstr "خلاصة المتابعة"
|
||||
|
||||
#. module: account_followup
|
||||
#: view:account.followup.print:0
|
||||
|
@ -535,7 +543,7 @@ msgstr "مستوى اعلى متابعة"
|
|||
#. module: account_followup
|
||||
#: model:ir.actions.act_window,name:account_followup.action_view_account_followup_followup_form
|
||||
msgid "Review Invoicing Follow-Ups"
|
||||
msgstr ""
|
||||
msgstr "استعراض متابعة الفوترة"
|
||||
|
||||
#. module: account_followup
|
||||
#: constraint:account.move.line:0
|
||||
|
@ -595,7 +603,7 @@ msgstr "الوصف"
|
|||
#. module: account_followup
|
||||
#: constraint:account_followup.followup:0
|
||||
msgid "Only One Followup by Company."
|
||||
msgstr ""
|
||||
msgstr "متابعة واحدة غقط من الشركة."
|
||||
|
||||
#. module: account_followup
|
||||
#: view:account_followup.stat:0
|
||||
|
|
|
@ -86,24 +86,24 @@ class account_payment_populate_statement(osv.osv_memory):
|
|||
'name': line.name,
|
||||
'partner_id': line.partner_id.id,
|
||||
'journal_id': statement.journal_id.id,
|
||||
'account_id': result.get('account_id', statement.journal_id.default_credit_account_id.id),
|
||||
'account_id': result['value'].get('account_id', statement.journal_id.default_credit_account_id.id),
|
||||
'company_id': statement.company_id.id,
|
||||
'currency_id': statement.currency.id,
|
||||
'date': line.date or time.strftime('%Y-%m-%d'),
|
||||
'amount': abs(amount),
|
||||
'period_id': statement.period_id.id
|
||||
'period_id': statement.period_id.id,
|
||||
}
|
||||
voucher_id = voucher_obj.create(cr, uid, voucher_res, context=context)
|
||||
voucher_line_dict = False
|
||||
if result['value']['line_ids']:
|
||||
for line_dict in result['value']['line_ids']:
|
||||
move_line = move_line_obj.browse(cr, uid, line_dict['move_line_id'], context)
|
||||
if line.move_line_id.move_id.id == move_line.move_id.id:
|
||||
voucher_line_dict = line_dict
|
||||
|
||||
voucher_line_dict = {}
|
||||
for line_dict in result['value']['line_cr_ids'] + result['value']['line_dr_ids']:
|
||||
move_line = move_line_obj.browse(cr, uid, line_dict['move_line_id'], context)
|
||||
if line.move_line_id.move_id.id == move_line.move_id.id:
|
||||
voucher_line_dict = line_dict
|
||||
|
||||
if voucher_line_dict:
|
||||
voucher_line_dict.update({'voucher_id': voucher_id})
|
||||
voucher_line_obj.create(cr, uid, voucher_line_dict, context=context)
|
||||
|
||||
st_line_id = statement_line_obj.create(cr, uid, {
|
||||
'name': line.order_id.reference or '?',
|
||||
'amount': - amount,
|
||||
|
|
|
@ -547,6 +547,19 @@ class account_voucher(osv.osv):
|
|||
vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
|
||||
for key in vals.keys():
|
||||
res[key].update(vals[key])
|
||||
#TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
|
||||
# [pre_line, line_cr_ids, payment_rate...] for type purchase.
|
||||
# We should definitively split account.voucher object in two and make distinct on_change functions. In the
|
||||
# meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
|
||||
# onchange returns a value for them
|
||||
if ttype == 'sale':
|
||||
del(res['value']['line_dr_ids'])
|
||||
del(res['value']['pre_line'])
|
||||
del(res['value']['payment_rate'])
|
||||
elif ttype == 'purchase':
|
||||
del(res['value']['line_cr_ids'])
|
||||
del(res['value']['pre_line'])
|
||||
del(res['value']['payment_rate'])
|
||||
return res
|
||||
|
||||
def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
|
||||
|
@ -588,7 +601,7 @@ class account_voucher(osv.osv):
|
|||
|
||||
#set default values
|
||||
default = {
|
||||
'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
|
||||
'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
|
||||
}
|
||||
|
||||
#drop existing lines
|
||||
|
@ -667,6 +680,7 @@ class account_voucher(osv.osv):
|
|||
|
||||
#voucher line creation
|
||||
for line in account_move_lines:
|
||||
|
||||
if _remove_noise_in_o2m():
|
||||
continue
|
||||
|
||||
|
@ -771,8 +785,10 @@ class account_voucher(osv.osv):
|
|||
if account_id and account_id.tax_ids:
|
||||
tax_id = account_id.tax_ids[0].id
|
||||
|
||||
vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
|
||||
vals['value'].update({'tax_id':tax_id,'amount': amount})
|
||||
vals = {'value':{} }
|
||||
if ttype in ('sale', 'purchase'):
|
||||
vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
|
||||
vals['value'].update({'tax_id':tax_id,'amount': amount})
|
||||
currency_id = False
|
||||
if journal.currency:
|
||||
currency_id = journal.currency.id
|
||||
|
@ -1183,6 +1199,7 @@ class account_voucher(osv.osv):
|
|||
account_id = voucher_brw.partner_id.property_account_receivable.id
|
||||
else:
|
||||
account_id = voucher_brw.partner_id.property_account_payable.id
|
||||
sign = voucher_brw.type == 'payment' and -1 or 1
|
||||
move_line = {
|
||||
'name': write_off_name or name,
|
||||
'account_id': account_id,
|
||||
|
@ -1191,7 +1208,7 @@ class account_voucher(osv.osv):
|
|||
'date': voucher_brw.date,
|
||||
'credit': diff > 0 and diff or 0.0,
|
||||
'debit': diff < 0 and -diff or 0.0,
|
||||
'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
|
||||
'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
|
||||
'currency_id': company_currency <> current_currency and current_currency or False,
|
||||
'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- where and when is this view used?? -->
|
||||
<record model="ir.ui.view" id="view_voucher_form">
|
||||
<field name="name">account.voucher.form</field>
|
||||
<field name="model">account.voucher</field>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
-
|
||||
In order to check account voucher module in OpenERP I create a customer voucher
|
||||
-
|
||||
!record {model: account.voucher, id: account_voucher_voucherforaxelor0}:
|
||||
!record {model: account.voucher, id: account_voucher_voucherforaxelor0, view: view_sale_receipt_form}:
|
||||
type: sale
|
||||
account_id: account.cash
|
||||
amount: 1000.0
|
||||
company_id: base.main_company
|
||||
|
@ -47,7 +48,8 @@
|
|||
-
|
||||
Now I create a Vendor Voucher
|
||||
-
|
||||
!record {model: account.voucher, id: account_voucher_voucheraxelor0, view: False}:
|
||||
!record {model: account.voucher, id: account_voucher_voucheraxelor0, view: view_voucher_filter_vendor}:
|
||||
type: purchase
|
||||
account_id: account.cash
|
||||
amount: 1000.0
|
||||
company_id: base.main_company
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
-
|
||||
Demo for Account Voucher
|
||||
-
|
||||
!record {model: account.voucher, id: account_voucher_voucheraxelor0again}:
|
||||
!record {model: account.voucher, id: account_voucher_voucheraxelor0again, view: view_sale_receipt_form}:
|
||||
type: sale
|
||||
account_id: account.cash
|
||||
company_id: base.main_company
|
||||
journal_id: account.bank_journal
|
||||
|
|
|
@ -216,6 +216,10 @@
|
|||
assert move_line.credit == 63.00, "Debtor account has wrong entry."
|
||||
elif move_line.amount_currency == 10.00:
|
||||
assert move_line.debit == 9.00, "Writeoff amount is wrong."
|
||||
elif move_line.amount_currency == 240.00:
|
||||
assert move_line.debit == 216.00, "Bank entry is wrong."
|
||||
else:
|
||||
assert False, "Unrecognized journal entry"
|
||||
-
|
||||
I check the residual amount of Invoice1, should be 20 in amount_currency
|
||||
-
|
||||
|
@ -237,7 +241,7 @@
|
|||
-
|
||||
On the first April, I create the second voucher of payment with values 45 USD, journal USD,
|
||||
-
|
||||
!record {model: account.voucher, id: account_voucher_2_case1}:
|
||||
!record {model: account.voucher, id: account_voucher_2_case1, view: view_vendor_receipt_form}:
|
||||
account_id: account.cash
|
||||
amount: 45.0
|
||||
company_id: base.main_company
|
||||
|
@ -323,8 +327,16 @@
|
|||
assert move_line.debit == 4.75, "Writeoff amount is wrong."
|
||||
elif move_line.debit == 11.5 and move_line.account_id.reconcile:
|
||||
assert move_line.amount_currency == 0.0 and move_line.reconcile_id.id == reconcile_a, "Exchange difference entry for the invoice of 100$ is wrong"
|
||||
elif move_line.credit == 11.5:
|
||||
assert move_line.amount_currency == 0.0
|
||||
elif move_line.debit == 31.0 and move_line.account_id.reconcile:
|
||||
assert move_line.amount_currency == 0.0 and move_line.reconcile_id.id == reconcile_b, "Exchange difference entry for the invoice of 200$ is wrong"
|
||||
elif move_line.credit == 31.0:
|
||||
assert move_line.amount_currency == 0.0
|
||||
elif move_line.amount_currency == 45.00:
|
||||
assert move_line.debit == 42.75, "Bank entry is wrong."
|
||||
else:
|
||||
assert False, "Unrecognized journal entry"
|
||||
-
|
||||
I check the residual amount of Invoice1, should be 0 in residual currency and 0 in amount_residual and paid
|
||||
-
|
||||
|
|
|
@ -164,7 +164,7 @@
|
|||
-
|
||||
I check that my currency rate difference is correct. 0 in debit with no amount_currency
|
||||
-
|
||||
I check that my writeoff is correct. 11.05 credit and 13.26 amount_currency
|
||||
I check that my writeoff is correct. 11.05 credit and -13.26 amount_currency
|
||||
-
|
||||
!python {model: account.voucher}: |
|
||||
voucher = self.search(cr, uid, [('name', '=', 'First payment: Case 4'), ('partner_id', '=', ref('base.res_partner_19'))])
|
||||
|
@ -179,7 +179,7 @@
|
|||
elif move_line.debit == 0.00 and move_line.credit == 0.00:
|
||||
assert move_line.amount_currency == 0.00, "Incorrect Currency Difference."
|
||||
elif move_line.credit == 10.61:
|
||||
assert move_line.amount_currency == 13.26, "Writeoff amount is wrong."
|
||||
assert move_line.amount_currency == -13.26, "Writeoff amount is wrong."
|
||||
else:
|
||||
assert False, "Wrong entry"
|
||||
-
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
-
|
||||
Creating a Voucher Receipt for partner Seagate with amount 30000.0
|
||||
-
|
||||
!record {model: account.voucher, id: account_voucher_seagate_0}:
|
||||
!record {model: account.voucher, id: account_voucher_seagate_0, view: view_sale_receipt_form}:
|
||||
account_id: account.a_recv
|
||||
amount: 30000.0
|
||||
company_id: base.main_company
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
<h1><label for="number" string="Sale Receipt"/> <field name="number" class="oe_inline" readonly="1"/></h1>
|
||||
<group>
|
||||
<group>
|
||||
<field name="type" invisible="True"/>
|
||||
<field name="partner_id" domain="[('customer','=',True)]" on_change="onchange_partner_id(partner_id, journal_id, amount, currency_id, type, date, context)" string="Customer" context="{'search_default_customer':1, 'show_address': 1}" options='{"always_reload": true}'/>
|
||||
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
|
@ -80,9 +81,9 @@
|
|||
<field name="date" on_change="onchange_date(date, currency_id, currency_id, amount, company_id, context)"/>
|
||||
<field name="name"/>
|
||||
<field name="paid" invisible="1"/>
|
||||
<field name="paid_amount_in_company_currency" invisible="1"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group>
|
||||
<field name="type" invisible="True"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Sales Information">
|
||||
|
@ -235,6 +236,7 @@
|
|||
widget="selection"
|
||||
on_change="onchange_journal(journal_id, line_dr_ids, tax_id, partner_id, date, amount, type, company_id, context)"
|
||||
groups="account.group_account_user"/>
|
||||
<field name="paid_amount_in_company_currency" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
|
|
|
@ -125,7 +125,8 @@ class account_analytic_account(osv.osv):
|
|||
if account.company_id:
|
||||
if account.company_id.currency_id.id != value:
|
||||
raise osv.except_osv(_('Error!'), _("If you set a company, the currency selected has to be the same as it's currency. \nYou can remove the company belonging, and thus change the currency, only on analytic account of type 'view'. This can be really usefull for consolidation purposes of several companies charts with different currencies, for example."))
|
||||
return cr.execute("""update account_analytic_account set currency_id=%s where id=%s""", (value, account.id, ))
|
||||
if value:
|
||||
return cr.execute("""update account_analytic_account set currency_id=%s where id=%s""", (value, account.id, ))
|
||||
|
||||
def _currency(self, cr, uid, ids, field_name, arg, context=None):
|
||||
result = {}
|
||||
|
@ -163,7 +164,7 @@ class account_analytic_account(osv.osv):
|
|||
'date': fields.date('Date End', select=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=False), #not required because we want to allow different companies to use the same chart of account, except for leaf accounts.
|
||||
'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','To Renew'),('close','Closed')], 'Status', required=True,),
|
||||
'currency_id': fields.function(_currency, fnct_inv=_set_company_currency,
|
||||
'currency_id': fields.function(_currency, fnct_inv=_set_company_currency, #the currency_id field is readonly except if it's a view account and if there is no company
|
||||
store = {
|
||||
'res.company': (_get_analytic_account, ['currency_id'], 10),
|
||||
}, string='Currency', type='many2one', relation='res.currency'),
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<field name="partner_id" on_change="on_change_partner_id(partner_id, name)" attrs="{'required':[('type','=','contract')]}"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="code"/>
|
||||
<field name="currency_id" attrs="{'invisible': ['|',('type', '<>', 'view'), ('company_id', '<>', False)]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="type"/>
|
||||
|
|
|
@ -34,6 +34,7 @@ Allow users to login through Google OAuth2.
|
|||
'depends': ['base', 'web', 'base_setup'],
|
||||
'data': [
|
||||
'auth_oauth_data.xml',
|
||||
'auth_oauth_data.yml',
|
||||
'auth_oauth_view.xml',
|
||||
'security/ir.model.access.csv'
|
||||
],
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
-
|
||||
Use database uuid as client_id for OpenERP oauth provider
|
||||
-
|
||||
!python {model: ir.config_parameter}: |
|
||||
oauth = self.pool.get('auth.oauth.provider')
|
||||
oauth.write(cr, uid, [ref('provider_openerp')], {'client_id': self.get_param(cr, uid, 'database.uuid')})
|
|
@ -1 +1,23 @@
|
|||
import auth_reset_password
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import controllers
|
||||
import res_users
|
||||
|
|
|
@ -1,3 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{
|
||||
'name': 'Reset Password',
|
||||
'description': """
|
||||
|
@ -9,9 +30,11 @@ Allow users to reset their password from the login page.
|
|||
'category': 'Authentication',
|
||||
'website': 'http://www.openerp.com',
|
||||
'installable': True,
|
||||
'depends': ['auth_anonymous', 'email_template'],
|
||||
'data': ['auth_reset_password.xml'],
|
||||
'depends': ['auth_signup', 'email_template'],
|
||||
'data': [
|
||||
'auth_reset_password.xml',
|
||||
'res_users_view.xml',
|
||||
],
|
||||
'js': ['static/src/js/reset_password.js'],
|
||||
'css': ['static/src/css/reset_password.css'],
|
||||
'qweb': ['static/src/xml/reset_password.xml'],
|
||||
}
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
import base64
|
||||
import hashlib
|
||||
import simplejson
|
||||
import time
|
||||
import urlparse
|
||||
|
||||
from openerp.tools import config
|
||||
from openerp.osv import osv, fields
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
TWENTY_FOUR_HOURS = 24 * 60 * 60
|
||||
|
||||
def message_sign(data, secret):
|
||||
src = simplejson.dumps([data, secret], indent=None, separators=(',', ':'), sort_keys=True)
|
||||
sign = hashlib.sha1(src).hexdigest()
|
||||
msg = simplejson.dumps([data, sign], indent=None, separators=(',', ':'), sort_keys=True)
|
||||
# pad message to avoid '='
|
||||
pad = (3 - len(msg) % 3) % 3
|
||||
msg = msg + " " * pad
|
||||
msg = base64.urlsafe_b64encode(msg)
|
||||
return msg, sign
|
||||
|
||||
def message_check(msg, secret):
|
||||
msg = base64.urlsafe_b64decode(msg)
|
||||
l = simplejson.loads(msg)
|
||||
msg_data = l[0]
|
||||
msg_sign = l[1]
|
||||
tmp, sign = message_sign(msg_data, secret)
|
||||
if msg_sign == sign:
|
||||
return msg_data
|
||||
|
||||
class res_users(osv.osv):
|
||||
_inherit = 'res.users'
|
||||
|
||||
def _auth_reset_password_secret(self, cr, uid, context=None):
|
||||
uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid')
|
||||
res = {
|
||||
'dbname': cr.dbname,
|
||||
'uuid': uuid,
|
||||
'admin_passwd': config['admin_passwd']
|
||||
}
|
||||
return res
|
||||
|
||||
def _auth_reset_password_host(self, cr, uid, context=None):
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', '')
|
||||
|
||||
def _auth_reset_password_link(self, cr, uid, ids, context=None):
|
||||
assert len(ids) == 1
|
||||
host = self._auth_reset_password_host(cr, uid, context)
|
||||
secret = self._auth_reset_password_secret(cr, uid, context)
|
||||
msg_src = {
|
||||
'time': time.time(),
|
||||
'uid': ids[0],
|
||||
}
|
||||
msg, sign = message_sign(msg_src, secret)
|
||||
link = urlparse.urljoin(host, '/login?db=%s&login=anonymous&key=anonymous#action=reset_password&token=%s' % (cr.dbname, msg))
|
||||
return link
|
||||
|
||||
def _auth_reset_password_check_token(self, cr, uid, token, context=None):
|
||||
secret = self._auth_reset_password_secret(cr, uid, context)
|
||||
data = message_check(token, secret)
|
||||
if data and (time.time() - data['time'] < TWENTY_FOUR_HOURS):
|
||||
return data
|
||||
return None
|
||||
|
||||
def _auth_reset_password_send_email(self, cr, uid, email_to, tpl_name, res_id, context=None):
|
||||
model, tpl_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_reset_password', tpl_name)
|
||||
assert model == 'email.template'
|
||||
|
||||
msg_id = self.pool.get(model).send_mail(cr, uid, tpl_id, res_id, force_send=False, context=context)
|
||||
MailMessage = self.pool.get('mail.message')
|
||||
MailMessage.write(cr, uid, [msg_id], {'email_to': email_to}, context=context)
|
||||
MailMessage.send(cr, uid, [msg_id], context=context)
|
||||
|
||||
def send_reset_password_request(self, cr, uid, email, context=None):
|
||||
# TODO reseting a password knowing only an email is not good enough (email can be shared between multiple logins).
|
||||
ids = self.pool.get('res.users').search(cr, SUPERUSER_ID, [('user_email', '=', email)], context=context)
|
||||
if ids:
|
||||
self._auth_reset_password_send_email(cr, SUPERUSER_ID, email, 'reset_password_email', ids[0], context=context)
|
||||
return True
|
||||
#else:
|
||||
# _m, company_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'main_company')
|
||||
# self._auth_reset_password_send_email(cr, uid, email, 'email_no_user', company_id, context=context)
|
||||
return False
|
||||
|
||||
class auth_reset_password(osv.TransientModel):
|
||||
_name = 'auth.reset_password'
|
||||
_rec_name = 'password'
|
||||
_columns = {
|
||||
'password': fields.char('Password', size=64),
|
||||
'password_confirmation': fields.char('Confirm Password', size=64),
|
||||
'token': fields.char('Token', size=128),
|
||||
'state': fields.selection([(x, x) for x in 'draft done missmatch error'.split()], required=True),
|
||||
}
|
||||
_defaults = {
|
||||
'state': 'draft',
|
||||
}
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
# NOTE here, invalid values raises exceptions to avoid storing
|
||||
# sensitive data into the database (which then are available to anyone)
|
||||
|
||||
pw = values.get('password')
|
||||
if not pw or pw != values.get('password_confirmation'):
|
||||
raise osv.except_osv('Error', 'Passwords missmatch')
|
||||
|
||||
Users = self.pool.get('res.users')
|
||||
data = Users._auth_reset_password_check_token(cr, uid, values.get('token', ''))
|
||||
if data:
|
||||
Users.write(cr, SUPERUSER_ID, data['uid'], {'password': pw}, context=context)
|
||||
else:
|
||||
raise osv.except_osv('Error', 'Invalid token')
|
||||
|
||||
# Dont store password
|
||||
values = {'state': 'done'}
|
||||
return super(auth_reset_password, self).create(cr, uid, values, context)
|
||||
|
||||
def change(self, cr, uid, ids, context=None):
|
||||
return True
|
||||
|
||||
def onchange_pw(self, cr, uid, ids, password, password_confirmation, context=None):
|
||||
if password != password_confirmation:
|
||||
return {'value': {'state': 'missmatch'}}
|
||||
return {'value': {'state': 'draft'}}
|
||||
|
||||
def onchange_token(self, cr, uid, ids, token, context=None):
|
||||
Users = self.pool.get('res.users')
|
||||
if not Users._auth_reset_password_check_token(cr, uid, token, context=context):
|
||||
return {'value': {'state': 'error'}}
|
||||
return {}
|
|
@ -7,56 +7,15 @@
|
|||
<field name="name">Reset Password</field>
|
||||
<field name="model_id" ref="base.model_res_users"/>
|
||||
<field name="email_from"><![CDATA[${object.company_id.name} <${object.company_id.email}>]]></field>
|
||||
<field name="email_to" eval="False"><!--(set by reset_password module)--></field>
|
||||
<field name="email_to">${object.email}</field>
|
||||
<field name="subject">Password reset</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<p>A password reset was requested the OpenERP account linked to this email on ${object._auth_reset_password_host()}</p>
|
||||
<p>A password reset was requested for the OpenERP account linked to this email.</p>
|
||||
|
||||
<p>You may change your password following this <a href="${object._auth_reset_password_link()}">link</a>,
|
||||
or by copy-pasting the following URL in your browser: ${object._auth_reset_password_link()}</p>
|
||||
<p>You may change your password following <a href="${object.signup_url}">this link</a>.</p>
|
||||
|
||||
<p>Note: If you did not ask for a password reset, you can safely ignore this email.</p>]]></field>
|
||||
</record>
|
||||
|
||||
<!-- TODO get own css -->
|
||||
<record id="reset_password_wizard_form_view" model="ir.ui.view">
|
||||
<field name="name">auth.reset_password.form</field>
|
||||
<field name="model">auth.reset_password</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Reset Password" version="7.0">
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="token" on_change="onchange_token(token)" invisible="1"/>
|
||||
<group colspan="4" states="draft,missmatch">
|
||||
<field name="password" required='1' on_change="onchange_pw(password,password_confirmation)"/>
|
||||
<field name="password_confirmation" required='1' on_change="onchange_pw(password,password_confirmation)"/>
|
||||
<group colspan="4" states="missmatch">
|
||||
<div>Passwords missmatch</div>
|
||||
</group>
|
||||
<group colspan="2" col="1">
|
||||
<button string="Change Password" name="change" icon="gtk-dialog-authentication" attrs="{'readonly': [('state', '=', 'missmatch')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group colspan="4" states="error" col="1">
|
||||
<div>Invalid or expired token</div>
|
||||
<button special="cancel" string="Close"/>
|
||||
</group>
|
||||
<group colspan="4" states="done" col="1">
|
||||
<div>Password changed. We sent you an email confirming the password change.</div>
|
||||
<button special="cancel" string="Close"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_reset" model="ir.actions.act_window">
|
||||
<field name="name">Reset Password</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">auth.reset_password</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import main
|
||||
|
||||
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.modules.registry import RegistryManager
|
||||
import openerp.addons.web.common.http as openerpweb
|
||||
|
||||
import werkzeug
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class Controller(openerpweb.Controller):
|
||||
_cp_path = '/auth_reset_password'
|
||||
|
||||
@openerpweb.httprequest
|
||||
def reset_password(self, req, dbname, login):
|
||||
""" retrieve user, and perform reset password """
|
||||
url = '/'
|
||||
registry = RegistryManager.get(dbname)
|
||||
with registry.cursor() as cr:
|
||||
try:
|
||||
res_users = registry.get('res.users')
|
||||
res_users.reset_password(cr, SUPERUSER_ID, login)
|
||||
cr.commit()
|
||||
message = 'An email has been sent with credentials to reset your password'
|
||||
except Exception as e:
|
||||
# signup error
|
||||
_logger.exception('error when resetting password')
|
||||
message = e.message
|
||||
url = "/#action=login&error_message=%s" % werkzeug.urls.url_quote(message)
|
||||
return werkzeug.utils.redirect(url)
|
||||
|
||||
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,59 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
def now(**kwargs):
|
||||
dt = datetime.now() + timedelta(**kwargs)
|
||||
return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
|
||||
class res_users(osv.osv):
|
||||
_inherit = 'res.users'
|
||||
|
||||
def reset_password(self, cr, uid, login, context=None):
|
||||
""" retrieve the user corresponding to login (login or email),
|
||||
and reset their password
|
||||
"""
|
||||
user_ids = self.search(cr, uid, [('login', '=', login)], context=context)
|
||||
if not user_ids:
|
||||
user_ids = self.search(cr, uid, [('email', '=', login)], context=context)
|
||||
if len(user_ids) != 1:
|
||||
raise Exception('Reset password: invalid username or email')
|
||||
return self.action_reset_password(cr, uid, user_ids, context=context)
|
||||
|
||||
def action_reset_password(self, cr, uid, ids, context=None):
|
||||
""" create signup token for each user, and send their signup url by email """
|
||||
# prepare reset password signup
|
||||
res_partner = self.pool.get('res.partner')
|
||||
partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context)]
|
||||
res_partner.signup_prepare(cr, uid, partner_ids, expiration=now(days=+1), context=context)
|
||||
|
||||
# send email to users with their signup url
|
||||
template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_reset_password', 'reset_password_email')
|
||||
assert template._name == 'email.template'
|
||||
for user in self.browse(cr, uid, ids, context):
|
||||
self.pool.get('email.template').send_mail(cr, uid, template.id, user.id, context=context)
|
||||
|
||||
return True
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="res_users_form_view" model="ir.ui.view">
|
||||
<field name="name">user.form.reset_password</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="base.view_users_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet/*[1]" position="before">
|
||||
<div class="oe_right oe_button_box">
|
||||
<button string="Reset Password" type="object" name="action_reset_password"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -1,12 +0,0 @@
|
|||
.openerp .oe_login .oe_login_pane {
|
||||
height: 152px;
|
||||
}
|
||||
.openerp .oe_login .oe_login_pane ul.oe_login_switch a {
|
||||
color: #eeeeee;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.openerp .oe_login .oe_login_pane ul.oe_login_switch a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
|
@ -1,72 +1,30 @@
|
|||
openerp.auth_reset_password = function(instance) {
|
||||
var _t = instance.web._t;
|
||||
|
||||
instance.web.Login.include({
|
||||
start: function() {
|
||||
var $e = this.$el;
|
||||
$e.find('a.oe_login_switch').click(function() {
|
||||
$e.find('ul.oe_login_switch').toggle();
|
||||
var $m = $e.find('form input[name=is_reset_pw]');
|
||||
$m.attr('checked', !$m.is(':checked'));
|
||||
});
|
||||
this.$('a.oe_reset_password').click(this.do_reset_password);
|
||||
return this._super();
|
||||
},
|
||||
on_submit: function(ev) {
|
||||
if(ev) {
|
||||
do_reset_password: function(ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
var $e = this.$el;
|
||||
var db = $e.find("form [name=db]").val();
|
||||
var db = this.$("form [name=db]").val();
|
||||
var login = this.$("form input[name=login]").val();
|
||||
if (!db) {
|
||||
this.do_warn(_t("Login"), _t("No database selected !"));
|
||||
this.do_warn("Login", "No database selected !");
|
||||
return false;
|
||||
} else if (!login) {
|
||||
this.do_warn("Login", "Please enter a username or email address.")
|
||||
return false;
|
||||
}
|
||||
var $m = $e.find('form input[name=is_reset_pw]');
|
||||
if ($m.is(':checked')) {
|
||||
var email = $e.find('form input[name=email]').val();
|
||||
return this.do_reset_password(db, email);
|
||||
} else {
|
||||
return this._super(ev);
|
||||
}
|
||||
},
|
||||
do_reset_password: function(db, email) {
|
||||
var self = this;
|
||||
instance.session.session_authenticate(db, 'anonymous', 'anonymous', true).pipe(function () {
|
||||
var func = new instance.web.Model("res.users").get_func("send_reset_password_request");
|
||||
return func(email).then(function(res) {
|
||||
// show message
|
||||
self.do_notify(_t('Reset Password'), _.str.sprintf(_t('We have sent an email to %s with further instructions'), email), true);
|
||||
}, function(error, event) {
|
||||
// no traceback please
|
||||
event.preventDefault();
|
||||
});
|
||||
}).fail(function(error, event) {
|
||||
// cannot log as anonymous or reset_password not installed
|
||||
self.do_warn(_t('Reset Password'), _.str.sprintf(_t('Reset Password functionnality is not available for database %s'), db), true);
|
||||
});
|
||||
var params = {
|
||||
dbname : db,
|
||||
login: login,
|
||||
};
|
||||
var url = "/auth_reset_password/reset_password?" + $.param(params);
|
||||
window.location = url;
|
||||
}
|
||||
});
|
||||
|
||||
instance.reset_password = {};
|
||||
instance.reset_password.ResetPassword = instance.web.Widget.extend({
|
||||
init: function(parent, params) {
|
||||
this._super(parent);
|
||||
this.token = (params && params.token) || false;
|
||||
},
|
||||
start: function() {
|
||||
this.do_action({
|
||||
name: 'Reset Password',
|
||||
type: 'ir.actions.act_window',
|
||||
context: {default_token: this.token},
|
||||
res_model: 'auth.reset_password',
|
||||
target: 'new',
|
||||
views: [[false, 'form']]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
instance.web.client_actions.add("reset_password", "instance.reset_password.ResetPassword");
|
||||
|
||||
|
||||
};
|
||||
|
|
|
@ -1,26 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- vim:fdl=1:
|
||||
-->
|
||||
<!-- vim:fdl=1: -->
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-extend="Login">
|
||||
<t t-jquery="form ul:first">
|
||||
// addClass does not work :(
|
||||
this.attr('class', (this.attr('class') || '') + ' oe_login_switch');
|
||||
</t>
|
||||
<t t-jquery="form ul:first li:last" t-operation="after">
|
||||
<li>
|
||||
<a class="oe_login_switch" href="#">Forgot your password?</a>
|
||||
</li>
|
||||
</t>
|
||||
<t t-jquery="form ul:first" t-operation="after">
|
||||
<ul class="oe_login_switch" style="display:none;">
|
||||
<li style="display:none;"><input type="checkbox" name="is_reset_pw"/></li>
|
||||
<li>Email</li>
|
||||
<li><input type="email" name="email"/></li>
|
||||
<li><button name="submit">Reset Password</button></li>
|
||||
<li><a class="oe_login_switch" href="#">< Back</a></li>
|
||||
</ul>
|
||||
<li><a class="oe_reset_password" href="#">Reset password</a></li>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
|
@ -34,7 +34,9 @@ Allow users to sign up.
|
|||
'data': [
|
||||
'auth_signup_data.xml',
|
||||
'res_config.xml',
|
||||
'res_users_view.xml',
|
||||
],
|
||||
'js': ['static/src/js/auth_signup.js'],
|
||||
'css' : ['static/src/css/base.css'],
|
||||
'qweb': ['static/src/xml/auth_signup.xml'],
|
||||
}
|
||||
|
|
|
@ -1,35 +1,63 @@
|
|||
import logging
|
||||
|
||||
import werkzeug.urls
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.modules.registry import RegistryManager
|
||||
from openerp.addons.web.controllers.main import login_and_redirect
|
||||
import openerp.addons.web.common.http as openerpweb
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
import werkzeug
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class OpenIDController(openerpweb.Controller):
|
||||
class Controller(openerpweb.Controller):
|
||||
_cp_path = '/auth_signup'
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def retrieve(self, req, dbname, token):
|
||||
""" retrieve the user info (name, login or email) corresponding to a signup token """
|
||||
registry = RegistryManager.get(dbname)
|
||||
user_info = None
|
||||
with registry.cursor() as cr:
|
||||
res_partner = registry.get('res.partner')
|
||||
user_info = res_partner.signup_retrieve_info(cr, SUPERUSER_ID, token)
|
||||
return user_info
|
||||
|
||||
@openerpweb.httprequest
|
||||
def signup(self, req, dbname, name, login, password):
|
||||
def signup(self, req, dbname, token, name, login, password):
|
||||
""" sign up a user (new or existing), and log it in """
|
||||
url = '/'
|
||||
registry = RegistryManager.get(dbname)
|
||||
with registry.cursor() as cr:
|
||||
try:
|
||||
Users = registry.get('res.users')
|
||||
credentials = Users.auth_signup(cr, SUPERUSER_ID, name, login, password)
|
||||
res_users = registry.get('res.users')
|
||||
values = {'name': name, 'login': login, 'password': password}
|
||||
credentials = res_users.signup(cr, SUPERUSER_ID, values, token)
|
||||
cr.commit()
|
||||
return login_and_redirect(req, *credentials)
|
||||
except AttributeError:
|
||||
# auth_signup is not installed
|
||||
_logger.exception('attribute error when signup')
|
||||
url = "/#action=auth_signup&error=NA" # Not Available
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
# signup error
|
||||
_logger.exception('error when signup')
|
||||
url = "/#action=auth_signup&error=UE" # Unexcpected Error
|
||||
url = "/#action=login&error_message=%s" % werkzeug.urls.url_quote(e.message)
|
||||
return werkzeug.utils.redirect(url)
|
||||
|
||||
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -20,22 +20,27 @@
|
|||
##############################################################################
|
||||
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
|
||||
class base_config_settings(osv.TransientModel):
|
||||
_inherit = 'base.config.settings'
|
||||
|
||||
_columns = {
|
||||
'auth_signup_uninvited': fields.boolean('Allow public users to sign up', help="If unchecked only invited users may sign up"),
|
||||
'auth_signup_uninvited': fields.boolean('Allow external users to sign up', help="If unchecked, only invited users may sign up"),
|
||||
'auth_signup_template_user_id': fields.many2one('res.users', 'Template user for new users created through signup'),
|
||||
}
|
||||
|
||||
def get_default_auth_signup_template_user_id(self, cr, uid, fields, context=None):
|
||||
icp = self.pool.get('ir.config_parameter')
|
||||
# we use safe_eval on the result, since the value of the parameter is a nonempty string
|
||||
return {
|
||||
'auth_signup_template_user_id': icp.get_param(cr, uid, 'auth_signup.template_user_id', 0) or False
|
||||
'auth_signup_uninvited': safe_eval(icp.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')),
|
||||
'auth_signup_template_user_id': safe_eval(icp.get_param(cr, uid, 'auth_signup.template_user_id', 'False')),
|
||||
}
|
||||
|
||||
def set_auth_signup_template_user_id(self, cr, uid, ids, context=None):
|
||||
config = self.browse(cr, uid, ids[0], context=context)
|
||||
icp = self.pool.get('ir.config_parameter')
|
||||
icp.set_param(cr, uid, 'auth_signup.template_user_id', config.auth_signup_template_user_id.id)
|
||||
# we store the repr of the values, since the value of the parameter is a required string
|
||||
icp.set_param(cr, uid, 'auth_signup.allow_uninvited', repr(config.auth_signup_uninvited))
|
||||
icp.set_param(cr, uid, 'auth_signup.template_user_id', repr(config.auth_signup_template_user_id.id))
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
</div>
|
||||
<div attrs="{'invisible':[('auth_signup_uninvited','=',False)]}">
|
||||
<label for="auth_signup_template_user_id"/>
|
||||
<field name="auth_signup_template_user_id" class="oe_inline" domain="['|',('active','=',0),('active','=',1)]"/>
|
||||
<field name="auth_signup_template_user_id" class="oe_inline"
|
||||
attrs="{'required': [('auth_signup_uninvited', '=', True)]}"
|
||||
domain="['|',('active','=',0),('active','=',1)]"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
|
|
|
@ -1,47 +1,205 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import openerp
|
||||
from openerp.osv import osv
|
||||
from openerp.osv import osv, fields
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
|
||||
import time
|
||||
import random
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
def random_token():
|
||||
# the token has an entropy of about 120 bits (6 bits/char * 20 chars)
|
||||
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
return ''.join(random.choice(chars) for i in xrange(20))
|
||||
|
||||
def now():
|
||||
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
|
||||
class res_partner(osv.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
def _get_signup_valid(self, cr, uid, ids, name, arg, context=None):
|
||||
dt = now()
|
||||
res = {}
|
||||
for partner in self.browse(cr, uid, ids, context):
|
||||
res[partner.id] = bool(partner.signup_token) and \
|
||||
(not partner.signup_expiration or dt <= partner.signup_expiration)
|
||||
return res
|
||||
|
||||
def _get_signup_url(self, cr, uid, ids, name, arg, context=None):
|
||||
""" determine a signup url for a given partner """
|
||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
|
||||
# if required, make sure that every partner without user has a valid signup token
|
||||
if context and context.get('signup_valid'):
|
||||
unsigned_ids = [p.id for p in self.browse(cr, uid, ids, context) if not p.user_ids]
|
||||
self.signup_prepare(cr, uid, unsigned_ids, context=context)
|
||||
|
||||
res = dict.fromkeys(ids, False)
|
||||
for partner in self.browse(cr, uid, ids, context):
|
||||
if partner.signup_token:
|
||||
params = (urllib.quote(cr.dbname), urllib.quote(partner.signup_token))
|
||||
res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&token=%s" % params)
|
||||
elif partner.user_ids:
|
||||
user = partner.user_ids[0]
|
||||
params = (urllib.quote(cr.dbname), urllib.quote(user.login))
|
||||
res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&login=%s" % params)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'signup_token': fields.char(size=24, string='Signup Token'),
|
||||
'signup_expiration': fields.datetime(string='Signup Expiration'),
|
||||
'signup_valid': fields.function(_get_signup_valid, type='boolean', string='Signup Token is Valid'),
|
||||
'signup_url': fields.function(_get_signup_url, type='char', string='Signup URL'),
|
||||
}
|
||||
|
||||
def action_signup_prepare(self, cr, uid, ids, context=None):
|
||||
return self.signup_prepare(cr, uid, ids, context=context)
|
||||
|
||||
def signup_prepare(self, cr, uid, ids, expiration=False, context=None):
|
||||
""" generate a new token for the partners with the given validity, if necessary
|
||||
:param expiration: the expiration datetime of the token (string, optional)
|
||||
"""
|
||||
for partner in self.browse(cr, uid, ids, context):
|
||||
if expiration or not partner.signup_valid:
|
||||
token = random_token()
|
||||
while self._signup_retrieve_partner(cr, uid, token, context=context):
|
||||
token = random_token()
|
||||
partner.write({'signup_token': token, 'signup_expiration': expiration})
|
||||
return True
|
||||
|
||||
def _signup_retrieve_partner(self, cr, uid, token,
|
||||
check_validity=False, raise_exception=False, context=None):
|
||||
""" find the partner corresponding to a token, and possibly check its validity
|
||||
:param token: the token to resolve
|
||||
:param check_validity: if True, also check validity
|
||||
:param raise_exception: if True, raise exception instead of returning False
|
||||
:return: partner (browse record) or False (if raise_exception is False)
|
||||
"""
|
||||
partner_ids = self.search(cr, uid, [('signup_token', '=', token)], context=context)
|
||||
if not partner_ids:
|
||||
if raise_exception:
|
||||
raise Exception("Signup token '%s' is not valid" % token)
|
||||
return False
|
||||
partner = self.browse(cr, uid, partner_ids[0], context)
|
||||
if check_validity and not partner.signup_valid:
|
||||
if raise_exception:
|
||||
raise Exception("Signup token '%s' is no longer valid" % token)
|
||||
return False
|
||||
return partner
|
||||
|
||||
def signup_retrieve_info(self, cr, uid, token, context=None):
|
||||
""" retrieve the user info about the token
|
||||
:return: a dictionary with the user information:
|
||||
- 'db': the name of the database
|
||||
- 'token': the token, if token is valid
|
||||
- 'name': the name of the partner, if token is valid
|
||||
- 'login': the user login, if the user already exists
|
||||
- 'email': the partner email, if the user does not exist
|
||||
"""
|
||||
partner = self._signup_retrieve_partner(cr, uid, token, raise_exception=True, context=None)
|
||||
res = {'db': cr.dbname}
|
||||
if partner.signup_valid:
|
||||
res['token'] = token
|
||||
res['name'] = partner.name
|
||||
if partner.user_ids:
|
||||
res['login'] = partner.user_ids[0].login
|
||||
else:
|
||||
res['email'] = partner.email or ''
|
||||
return res
|
||||
|
||||
|
||||
|
||||
class res_users(osv.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
def auth_signup_create(self, cr, uid, new_user, context=None):
|
||||
# new_user:
|
||||
# login
|
||||
# email
|
||||
# name (optional)
|
||||
# partner_id (optional)
|
||||
# groups (optional)
|
||||
# sign (for partner_id and groups)
|
||||
#
|
||||
user_template_id = self.pool.get('ir.config_parameter').get_param(cr, uid, 'auth.signup_template_user_id', 0)
|
||||
if user_template_id:
|
||||
self.pool.get('res.users').copy(cr, SUPERUSER_ID, user_template_id, new_user, context=context)
|
||||
else:
|
||||
self.pool.get('res.users').create(cr, SUPERUSER_ID, new_user, context=context)
|
||||
def _get_state(self, cr, uid, ids, name, arg, context=None):
|
||||
return dict((user.id, 'new' if not user.login_date else 'reset' if user.signup_token else 'active')
|
||||
for user in self.browse(cr, uid, ids, context))
|
||||
|
||||
def auth_signup(self, cr, uid, name, login, password, context=None):
|
||||
r = (cr.dbname, login, password)
|
||||
res = self.search(cr, uid, [("login", "=", login)])
|
||||
if res:
|
||||
# Existing user
|
||||
user_id = res[0]
|
||||
try:
|
||||
self.check(cr.dbname, user_id, password)
|
||||
# Same password
|
||||
except openerp.exceptions.AccessDenied:
|
||||
# Different password
|
||||
raise
|
||||
else:
|
||||
# New user
|
||||
new_user = {
|
||||
'name': name,
|
||||
'login': login,
|
||||
'user_email': login,
|
||||
'password': password,
|
||||
'active': True,
|
||||
}
|
||||
self.auth_signup_create(cr, uid, new_user)
|
||||
return r
|
||||
_columns = {
|
||||
'state': fields.function(_get_state, string='State', type='selection',
|
||||
selection=[('new', 'New'), ('active', 'Active'), ('reset', 'Resetting Password')]),
|
||||
}
|
||||
|
||||
#
|
||||
def signup(self, cr, uid, values, token=None, context=None):
|
||||
""" signup a user, to either:
|
||||
- create a new user (no token), or
|
||||
- create a user for a partner (with token, but no user for partner), or
|
||||
- change the password of a user (with token, and existing user).
|
||||
:param values: a dictionary with field values
|
||||
:param token: signup token (optional)
|
||||
:return: (dbname, login, password) for the signed up user
|
||||
"""
|
||||
assert values.get('login') and values.get('password')
|
||||
result = (cr.dbname, values['login'], values['password'])
|
||||
|
||||
if token:
|
||||
# signup with a token: find the corresponding partner id
|
||||
res_partner = self.pool.get('res.partner')
|
||||
partner = res_partner._signup_retrieve_partner(cr, uid, token,
|
||||
check_validity=True, raise_exception=True, context=None)
|
||||
# invalidate signup token
|
||||
partner.write({'signup_token': False, 'signup_expiration': False})
|
||||
if partner.user_ids:
|
||||
# user exists, modify its password
|
||||
partner.user_ids[0].write({'password': values['password']})
|
||||
else:
|
||||
# user does not exist: sign up invited user
|
||||
self._signup_create_user(cr, uid, {
|
||||
'name': partner.name,
|
||||
'login': values['login'],
|
||||
'password': values['password'],
|
||||
'email': values['login'],
|
||||
'partner_id': partner.id,
|
||||
}, context=context)
|
||||
return result
|
||||
|
||||
# sign up an external user
|
||||
assert values.get('name'), 'Signup: no name given for new user'
|
||||
self._signup_create_user(cr, uid, {
|
||||
'name': values['name'],
|
||||
'login': values['login'],
|
||||
'password': values['password'],
|
||||
'email': values['login'],
|
||||
}, context=context)
|
||||
return result
|
||||
|
||||
def _signup_create_user(self, cr, uid, values, context=None):
|
||||
""" create a new user from the template user """
|
||||
ir_config_parameter = self.pool.get('ir.config_parameter')
|
||||
template_user_id = safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.template_user_id', 'False'))
|
||||
assert template_user_id and self.exists(cr, uid, template_user_id, context=context), 'Signup: invalid template user'
|
||||
|
||||
# check that uninvited users may sign up
|
||||
if 'partner_id' not in values:
|
||||
if not safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')):
|
||||
raise Exception('Signup is not allowed for uninvited users')
|
||||
|
||||
# create a copy of the template user (attached to a specific partner_id if given)
|
||||
values['active'] = True
|
||||
return self.copy(cr, uid, template_user_id, values, context=context)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="res_users_form_view" model="ir.ui.view">
|
||||
<field name="name">user.form.state</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="base.view_users_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet" position="before">
|
||||
<header>
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,3 @@
|
|||
base.css: base.sass
|
||||
sass --trace -t expanded base.sass base.css
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
@charset "utf-8";
|
||||
.openerp .oe_login .oe_signup_show {
|
||||
display: none;
|
||||
}
|
||||
.openerp .oe_login_signup .oe_signup_show {
|
||||
display: block !important;
|
||||
}
|
||||
.openerp .oe_login_signup .oe_signup_hide {
|
||||
display: none;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
@charset "utf-8"
|
||||
|
||||
.openerp
|
||||
// Regular login form
|
||||
.oe_login
|
||||
.oe_signup_show
|
||||
display: none
|
||||
// Signup form
|
||||
.oe_login_signup
|
||||
.oe_signup_show
|
||||
display: block !important
|
||||
.oe_signup_hide
|
||||
display: none
|
|
@ -5,56 +5,99 @@ openerp.auth_signup = function(instance) {
|
|||
instance.web.Login.include({
|
||||
start: function() {
|
||||
var self = this;
|
||||
this.$('a.oe_signup').click(function() {
|
||||
var dbname = self.$("form [name=db]").val();
|
||||
self.do_action({
|
||||
type: 'ir.actions.client',
|
||||
tag: 'auth_signup.signup',
|
||||
params: {'dbname': dbname},
|
||||
target: 'new',
|
||||
name: 'Sign up'
|
||||
});
|
||||
return true;
|
||||
var d = this._super();
|
||||
|
||||
// to switch between the signup and regular login form
|
||||
this.$('a.oe_signup_signup').click(function() {
|
||||
self.$el.addClass("oe_login_signup");
|
||||
});
|
||||
this.$('a.oe_signup_back').click(function() {
|
||||
self.$el.removeClass("oe_login_signup");
|
||||
delete self.params.token;
|
||||
});
|
||||
return this._super();
|
||||
},
|
||||
});
|
||||
|
||||
// if there is an error message in params, show it then forget it
|
||||
if (self.params.error_message) {
|
||||
this.show_error(self.params.error_message);
|
||||
delete self.params.error_message;
|
||||
}
|
||||
|
||||
instance.auth_signup.Signup = instance.web.Widget.extend({
|
||||
template: 'auth_signup.signup',
|
||||
init: function(parent, params) {
|
||||
this.params = params;
|
||||
return this._super();
|
||||
// in case of a signup, retrieve the user information from the token
|
||||
if (self.params.db && self.params.token) {
|
||||
d = self.rpc("/auth_signup/retrieve", {dbname: self.params.db, token: self.params.token})
|
||||
.done(self.on_token_loaded)
|
||||
.fail(self.on_token_failed);
|
||||
}
|
||||
return d;
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
this.$('input[name=password_confirmation]').keyup(function() {
|
||||
var v = $(this).val();
|
||||
var $b = self.$('button');
|
||||
if (_.isEmpty(v) || self.$('input[name=password]').val() === v) {
|
||||
$b.removeAttr('disabled');
|
||||
on_token_loaded: function(result) {
|
||||
// select the right the database
|
||||
this.selected_db = result.db;
|
||||
this.on_db_loaded({db_list: [result.db]});
|
||||
if (result.token) {
|
||||
// switch to signup mode, set user name and login
|
||||
this.$el.addClass("oe_login_signup");
|
||||
this.$("form input[name=name]").val(result.name).attr("readonly", "readonly");
|
||||
if (result.login) {
|
||||
this.$("form input[name=login]").val(result.login).attr("readonly", "readonly");
|
||||
} else {
|
||||
$b.attr('disabled', 'disabled');
|
||||
this.$("form input[name=login]").val(result.email);
|
||||
}
|
||||
});
|
||||
|
||||
this.$('form').submit(function(ev) {
|
||||
if(ev) {
|
||||
ev.preventDefault();
|
||||
} else {
|
||||
// remain in login mode, set login if present
|
||||
delete this.params.token;
|
||||
this.$("form input[name=login]").val(result.login || "");
|
||||
}
|
||||
},
|
||||
on_token_failed: function(result, ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
this.show_error("Invalid signup token");
|
||||
delete this.params.db;
|
||||
delete this.params.token;
|
||||
},
|
||||
on_submit: function(ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
if (this.$el.hasClass("oe_login_signup")) {
|
||||
// signup user (or reset password)
|
||||
var db = this.$("form [name=db]").val();
|
||||
var name = this.$("form input[name=name]").val();
|
||||
var login = this.$("form input[name=login]").val();
|
||||
var password = this.$("form input[name=password]").val();
|
||||
var confirm_password = this.$("form input[name=confirm_password]").val();
|
||||
if (!db) {
|
||||
this.do_warn("Login", "No database selected !");
|
||||
return false;
|
||||
} else if (!name) {
|
||||
this.do_warn("Login", "Please enter a name.")
|
||||
return false;
|
||||
} else if (!login) {
|
||||
this.do_warn("Login", "Please enter a username.")
|
||||
return false;
|
||||
} else if (!password || !confirm_password) {
|
||||
this.do_warn("Login", "Please enter a password and confirm it.")
|
||||
return false;
|
||||
} else if (password !== confirm_password) {
|
||||
this.do_warn("Login", "Passwords do not match; please retype them.")
|
||||
return false;
|
||||
}
|
||||
var params = {
|
||||
dbname : self.params.dbname,
|
||||
name: self.$('input[name=name]').val(),
|
||||
login: self.$('input[name=email]').val(),
|
||||
password: self.$('input[name=password]').val(),
|
||||
dbname : db,
|
||||
token: this.params.token || "",
|
||||
name: name,
|
||||
login: login,
|
||||
password: password,
|
||||
};
|
||||
var url = "/auth_signup/signup?" + $.param(params);
|
||||
window.location = url;
|
||||
});
|
||||
return this._super();
|
||||
}
|
||||
} else {
|
||||
// regular login
|
||||
this._super(ev);
|
||||
}
|
||||
},
|
||||
});
|
||||
instance.web.client_actions.add("auth_signup.signup", "instance.auth_signup.Signup");
|
||||
|
||||
};
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- vim:fdl=1:
|
||||
-->
|
||||
<!-- vim:fdl=1: -->
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-extend="Login">
|
||||
<t t-jquery="form ul:first li:last" t-operation="after">
|
||||
<li>
|
||||
<a class="oe_signup" href="#">Sign Up</a>
|
||||
</li>
|
||||
<t t-extend="Login">
|
||||
<t t-jquery="form ul:first li:contains('Username')" t-operation="before">
|
||||
<li class="oe_signup_show">Name</li>
|
||||
<li class="oe_signup_show"><input name="name" type="text"/></li>
|
||||
</t>
|
||||
<t t-jquery="form ul:first li:contains('Username')" t-operation="replace">
|
||||
<li class="oe_signup_hide">Username</li>
|
||||
<li class="oe_signup_show">Username (Email)</li>
|
||||
</t>
|
||||
<t t-jquery="form ul:first li:has(input[name=password])" t-operation="after">
|
||||
<li class="oe_signup_show">Confirm Password</li>
|
||||
<li class="oe_signup_show"><input name="confirm_password" type="password"/></li>
|
||||
</t>
|
||||
<t t-jquery="form ul:first li:has(button[name=submit])" t-operation="replace">
|
||||
<li class="oe_signup_hide"><button name="submit">Log in</button></li>
|
||||
<li class="oe_signup_show"><button name="submit">Sign in</button></li>
|
||||
</t>
|
||||
<t t-jquery="form ul:first li:last" t-operation="after">
|
||||
<li><a class="oe_signup_hide oe_signup_signup" href="#">Sign Up</a></li>
|
||||
<li><a class="oe_signup_show oe_signup_back" href="#">Back to Login</a></li>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="auth_signup.signup">
|
||||
<div>
|
||||
<form>
|
||||
Name = <input type="text" name="name"/><br/>
|
||||
Email = <input type="email" name="email"/><br/>
|
||||
Password = <input type="password" name="password"/><br/>
|
||||
Confirmation = <input type="password" name="password_confirmation"/><br/>
|
||||
<button type="submit" disabled="disabled">Signup</button>
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
|
||||
</templates>
|
||||
|
|
|
@ -39,11 +39,11 @@ months = {
|
|||
|
||||
def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
|
||||
"""
|
||||
Get recurrent dates based on Rule string considering exdate and start date
|
||||
@param rrulestring: Rulestring
|
||||
@param exdate: List of exception dates for rrule
|
||||
@param startdate: Startdate for computing recurrent dates
|
||||
@return: List of Recurrent dates
|
||||
Get recurrent dates based on Rule string considering exdate and start date.
|
||||
@param rrulestring: rulestring
|
||||
@param exdate: list of exception dates for rrule
|
||||
@param startdate: startdate for computing recurrent dates
|
||||
@return: list of Recurrent dates
|
||||
"""
|
||||
def todate(date):
|
||||
val = parser.parse(''.join((re.compile('\d')).findall(date)))
|
||||
|
@ -67,11 +67,12 @@ def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
|
|||
|
||||
def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
|
||||
"""
|
||||
This function converts virtual event id into real id of actual event
|
||||
@param base_calendar_id: Id of calendar
|
||||
@param with_date: If value passed to this param it will return dates based on value of withdate + base_calendar_id
|
||||
Convert a "virtual/recurring event id" (type string) into a real event id (type int).
|
||||
E.g. virtual/recurring event id is 4-20091201100000, so it will return 4.
|
||||
@param base_calendar_id: id of calendar
|
||||
@param with_date: if a value is passed to this param it will return dates based on value of withdate + base_calendar_id
|
||||
@return: real event id
|
||||
"""
|
||||
|
||||
if base_calendar_id and isinstance(base_calendar_id, (str, unicode)):
|
||||
res = base_calendar_id.split('-')
|
||||
|
||||
|
@ -89,12 +90,13 @@ def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
|
|||
|
||||
def real_id2base_calendar_id(real_id, recurrent_date):
|
||||
"""
|
||||
Convert real id of record into virtual id using recurrent_date
|
||||
e.g. real id is 1 and recurrent_date is 01-12-2009 10:00:00 then it will return
|
||||
1-20091201100000
|
||||
@return: real id with recurrent date.
|
||||
Convert a real event id (type int) into a "virtual/recurring event id" (type string).
|
||||
E.g. real event id is 1 and recurrent_date is set to 01-12-2009 10:00:00, so
|
||||
it will return 1-20091201100000.
|
||||
@param real_id: real event id
|
||||
@param recurrent_date: real event recurrent date
|
||||
@return: string containing the real id and the recurrent date
|
||||
"""
|
||||
|
||||
if real_id and recurrent_date:
|
||||
recurrent_date = time.strftime("%Y%m%d%H%M%S", \
|
||||
time.strptime(recurrent_date, "%Y-%m-%d %H:%M:%S"))
|
||||
|
@ -104,10 +106,10 @@ def real_id2base_calendar_id(real_id, recurrent_date):
|
|||
def _links_get(self, cr, uid, context=None):
|
||||
"""
|
||||
Get request link.
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: list of dictionary which contain object and name and id.
|
||||
@param cr: the current row, from the database cursor
|
||||
@param uid: the current user's ID for security checks
|
||||
@param context: a standard dictionary for contextual values
|
||||
@return: list of dictionary which contain object and name and id
|
||||
"""
|
||||
obj = self.pool.get('res.request.link')
|
||||
ids = obj.search(cr, uid, [])
|
||||
|
@ -216,9 +218,9 @@ class calendar_attendee(osv.osv):
|
|||
|
||||
def _get_address(self, name=None, email=None):
|
||||
"""
|
||||
Gives email information in ical CAL-ADDRESS type format
|
||||
@param name: Name for CAL-ADDRESS value
|
||||
@param email: Email address for CAL-ADDRESS value
|
||||
Gives email information in ical CAL-ADDRESS type format.
|
||||
@param name: name for CAL-ADDRESS value
|
||||
@param email: email address for CAL-ADDRESS value
|
||||
"""
|
||||
if name and email:
|
||||
name += ':'
|
||||
|
@ -226,13 +228,13 @@ class calendar_attendee(osv.osv):
|
|||
|
||||
def _compute_data(self, cr, uid, ids, name, arg, context=None):
|
||||
"""
|
||||
Compute data on function fields for attendee values .
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of calendar attendee’s IDs.
|
||||
@param name: name of field.
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: Dictionary of form {id: {'field Name': value'}}.
|
||||
Compute data on function fields for attendee values.
|
||||
@param cr: the current row, from the database cursor
|
||||
@param uid: the current user's ID for security checks
|
||||
@param ids: list of calendar attendee's IDs
|
||||
@param name: name of field
|
||||
@param context: a standard dictionary for contextual values
|
||||
@return: dictionary of form {id: {'field Name': value'}}
|
||||
"""
|
||||
name = name[0]
|
||||
result = {}
|
||||
|
@ -297,10 +299,10 @@ class calendar_attendee(osv.osv):
|
|||
def _links_get(self, cr, uid, context=None):
|
||||
"""
|
||||
Get request link for ref field in calendar attendee.
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param cr: the current row, from the database cursor
|
||||
@param uid: the current user's id for security checks
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: list of dictionary which contain object and name and id.
|
||||
@return: list of dictionary which contain object and name and id
|
||||
"""
|
||||
obj = self.pool.get('res.request.link')
|
||||
ids = obj.search(cr, uid, [])
|
||||
|
@ -310,10 +312,10 @@ class calendar_attendee(osv.osv):
|
|||
def _lang_get(self, cr, uid, context=None):
|
||||
"""
|
||||
Get language for language selection field.
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: list of dictionary which contain code and name and id.
|
||||
@param cr: the current row, from the database cursor
|
||||
@param uid: the current user's id for security checks
|
||||
@param context: a standard dictionary for contextual values
|
||||
@return: list of dictionary which contain code and name and id
|
||||
"""
|
||||
obj = self.pool.get('res.lang')
|
||||
ids = obj.search(cr, uid, [])
|
||||
|
@ -375,7 +377,6 @@ property or property parameter."),
|
|||
'ref': fields.reference('Event Ref', selection=_links_get, size=128),
|
||||
'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'state': 'needs-action',
|
||||
'role': 'req-participant',
|
||||
|
@ -388,12 +389,12 @@ property or property parameter."),
|
|||
|
||||
def get_ics_file(self, cr, uid, event_obj, context=None):
|
||||
"""
|
||||
Returns iCalendar file for the event invitation
|
||||
@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 event_obj: Event object (browse record)
|
||||
@param context: A standard dictionary for contextual values
|
||||
Returns iCalendar file for the event invitation.
|
||||
@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 event_obj: event object (browse record)
|
||||
@param context: a standard dictionary for contextual values
|
||||
@return: .ics file content
|
||||
"""
|
||||
res = None
|
||||
|
@ -471,7 +472,7 @@ property or property parameter."),
|
|||
def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
|
||||
"""
|
||||
Send mail for event invitation to event attendees.
|
||||
@param email_from: Email address for user sending the mail
|
||||
@param email_from: email address for user sending the mail
|
||||
@return: True
|
||||
"""
|
||||
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
|
||||
|
@ -517,11 +518,11 @@ property or property parameter."),
|
|||
def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
|
||||
"""
|
||||
Make entry on email and availbility on change of user_id field.
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of calendar attendee’s IDs.
|
||||
@param user_id: Changed value of User id
|
||||
@return: dictionary of value. which put value in email and availability fields.
|
||||
@param cr: the current row, from the database cursor
|
||||
@param uid: the current user's ID for security checks
|
||||
@param ids: list of calendar attendee's IDs
|
||||
@param user_id: changed value of User id
|
||||
@return: dictionary of values which put value in email and availability fields
|
||||
"""
|
||||
|
||||
if not user_id:
|
||||
|
@ -531,24 +532,25 @@ property or property parameter."),
|
|||
return {'value': {'email': user.email, 'availability':user.availability}}
|
||||
|
||||
def do_tentative(self, cr, uid, ids, context=None, *args):
|
||||
""" Makes event invitation as Tentative
|
||||
@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 calendar attendee’s IDs
|
||||
@param *args: Get Tupple value
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
Makes event invitation as Tentative.
|
||||
@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 calendar attendee's IDs
|
||||
@param *args: get Tupple value
|
||||
@param context: a standard dictionary for contextual values
|
||||
"""
|
||||
return self.write(cr, uid, ids, {'state': 'tentative'}, context)
|
||||
|
||||
def do_accept(self, cr, uid, ids, context=None, *args):
|
||||
"""
|
||||
Update state of invitation as Accepted and
|
||||
if the invited user is other then event user it will make a copy of this event for invited user
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of calendar attendee’s IDs.
|
||||
@param context: A standard dictionary for contextual values
|
||||
Update state of invitation as Accepted and if the invited user is other
|
||||
then event user it will make a copy of this event for invited user.
|
||||
@param cr: the current row, from the database cursor
|
||||
@param uid: the current user's ID for security checks
|
||||
@param ids: list of calendar attendee's IDs
|
||||
@param context: a standard dictionary for contextual values
|
||||
@return: True
|
||||
"""
|
||||
if context is None:
|
||||
|
@ -564,25 +566,28 @@ property or property parameter."),
|
|||
return True
|
||||
|
||||
def do_decline(self, cr, uid, ids, context=None, *args):
|
||||
""" Marks event invitation as Declined
|
||||
@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 calendar attendee’s IDs
|
||||
@param *args: Get Tupple value
|
||||
@param context: A standard dictionary for contextual values """
|
||||
"""
|
||||
Marks event invitation as Declined.
|
||||
@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 calendar attendee's IDs
|
||||
@param *args: get Tupple value
|
||||
@param context: a standard dictionary for contextual values
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
return self.write(cr, uid, ids, {'state': 'declined'}, context)
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
""" Overrides orm create method.
|
||||
"""
|
||||
Overrides orm create method.
|
||||
@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 vals: Get Values
|
||||
@param context: A standard dictionary for contextual values """
|
||||
|
||||
@param cr: the current row, from the database cursor
|
||||
@param uid: the current user's ID for security checks
|
||||
@param vals: get Values
|
||||
@param context: a standard dictionary for contextual values
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if not vals.get("email") and vals.get("cn"):
|
||||
|
@ -592,6 +597,7 @@ property or property parameter."),
|
|||
vals['cn'] = vals.get("cn")
|
||||
res = super(calendar_attendee, self).create(cr, uid, vals, context)
|
||||
return res
|
||||
|
||||
calendar_attendee()
|
||||
|
||||
class res_alarm(osv.osv):
|
||||
|
@ -630,8 +636,8 @@ true, it will allow you to hide the event alarm information without removing it.
|
|||
"""
|
||||
Create Alarm for event.
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of res alarm’s IDs.
|
||||
@param uid: the current user's ID for security checks,
|
||||
@param ids: List of res alarm's IDs.
|
||||
@param model: Model name.
|
||||
@param date: Event date
|
||||
@param context: A standard dictionary for contextual values
|
||||
|
@ -704,8 +710,8 @@ true, it will allow you to hide the event alarm information without removing it.
|
|||
"""
|
||||
Delete alarm specified in ids
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of res alarm’s IDs.
|
||||
@param uid: the current user's ID for security checks,
|
||||
@param ids: List of res alarm's IDs.
|
||||
@param model: Model name for which alarm is to be cleared.
|
||||
@return: True
|
||||
"""
|
||||
|
@ -715,12 +721,12 @@ true, it will allow you to hide the event alarm information without removing it.
|
|||
ir_obj = self.pool.get('ir.model')
|
||||
model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
|
||||
model_obj = self.pool.get(model)
|
||||
for datas in model_obj.browse(cr, uid, ids, context=context):
|
||||
alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', datas.id)])
|
||||
for data in model_obj.browse(cr, uid, ids, context=context):
|
||||
alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', data.id)])
|
||||
if alarm_ids:
|
||||
alarm_obj.unlink(cr, uid, alarm_ids)
|
||||
cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
|
||||
where id=%%s' % model_obj._table,(datas.id,))
|
||||
where id=%%s' % model_obj._table,(data.id,))
|
||||
return True
|
||||
|
||||
res_alarm()
|
||||
|
@ -773,7 +779,7 @@ class calendar_alarm(osv.osv):
|
|||
Overrides orm create method.
|
||||
@param self: The object pointer
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param vals: dictionary of fields value.{‘name_of_the_field’: value, ...}
|
||||
@param vals: dictionary of fields value.{'name_of_the_field': value, ...}
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: new record id for calendar_alarm.
|
||||
"""
|
||||
|
@ -798,8 +804,8 @@ class calendar_alarm(osv.osv):
|
|||
"""Scheduler for event reminder
|
||||
@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 calendar alarm’s IDs.
|
||||
@param uid: the current user's ID for security checks,
|
||||
@param ids: List of calendar alarm's IDs.
|
||||
@param use_new_cursor: False or the dbname
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
|
@ -891,8 +897,8 @@ class calendar_event(osv.osv):
|
|||
"""Returns duration and/or end date based on values passed
|
||||
@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 calendar event’s IDs.
|
||||
@param uid: the current user's ID for security checks,
|
||||
@param ids: List of calendar event's IDs.
|
||||
@param start_date: Starting date
|
||||
@param duration: Duration between start date and end date
|
||||
@param end_date: Ending Datee
|
||||
|
@ -967,14 +973,14 @@ class calendar_event(osv.osv):
|
|||
if not isinstance(ids, list):
|
||||
ids = [ids]
|
||||
|
||||
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:
|
||||
for data 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 = data['id']
|
||||
if data.get('interval', 0) < 0:
|
||||
raise osv.except_osv(_('Warning!'), _('Interval cannot be negative.'))
|
||||
if datas.get('count', 0) <= 0:
|
||||
if data.get('count', 0) <= 0:
|
||||
raise osv.except_osv(_('Warning!'), _('Count cannot be negative or 0.'))
|
||||
if datas['recurrency']:
|
||||
result[event] = self.compute_rule_string(datas)
|
||||
if data['recurrency']:
|
||||
result[event] = self.compute_rule_string(data)
|
||||
else:
|
||||
result[event] = ""
|
||||
return result
|
||||
|
@ -990,7 +996,6 @@ class calendar_event(osv.osv):
|
|||
super(calendar_event, obj).write(cr, uid, ids, data, context=context)
|
||||
return True
|
||||
|
||||
|
||||
_columns = {
|
||||
'id': fields.integer('ID', readonly=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
|
@ -1006,21 +1011,24 @@ class calendar_event(osv.osv):
|
|||
'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
|
||||
'Show Time as', states={'done': [('readonly', True)]}),
|
||||
'base_calendar_url': fields.char('Caldav URL', size=264),
|
||||
'state': fields.selection([('tentative', 'Tentative'),
|
||||
('cancelled', 'Cancelled'),
|
||||
('confirmed', 'Confirmed'),
|
||||
], 'Status', readonly=True),
|
||||
'state': fields.selection([
|
||||
('tentative', 'Tentative'),
|
||||
('cancelled', 'Cancelled'),
|
||||
('confirmed', 'Confirmed'),
|
||||
], 'Status', readonly=True),
|
||||
'exdate': fields.text('Exception Date/Times', help="This property \
|
||||
defines the list of date/time exceptions for a recurring calendar component."),
|
||||
'exrule': fields.char('Exception Rule', size=352, help="Defines a \
|
||||
rule or repeating pattern of time to exclude from the recurring rule."),
|
||||
'rrule': fields.function(_get_rulestring, type='char', size=124, \
|
||||
fnct_inv=_rrule_write, store=True, string='Recurrent Rule'),
|
||||
'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
|
||||
('weekly', 'Weekly'), ('monthly', 'Monthly'), \
|
||||
('yearly', 'Yearly'),],
|
||||
'Recurrency', states={'done': [('readonly', True)]},
|
||||
help="Let the event automatically repeat at that interval"),
|
||||
'rrule_type': fields.selection([
|
||||
('daily', 'Daily'),
|
||||
('weekly', 'Weekly'),
|
||||
('monthly', 'Monthly'),
|
||||
('yearly', 'Yearly')
|
||||
], 'Recurrency', states={'done': [('readonly', True)]},
|
||||
help="Let the event automatically repeat at that interval"),
|
||||
'alarm_id': fields.many2one('res.alarm', 'Reminder', states={'done': [('readonly', True)]},
|
||||
help="Set an alarm at this time, before the event occurs" ),
|
||||
'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
|
||||
|
@ -1028,7 +1036,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
'recurrent_id': fields.datetime('Recurrent ID date'),
|
||||
'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
|
||||
'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': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with organizer attribute of VEvent.
|
||||
'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
|
||||
'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)"),
|
||||
|
@ -1043,13 +1051,21 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
'select1': fields.selection([('date', 'Date of month'),
|
||||
('day', 'Day of month')], 'Option'),
|
||||
'day': fields.integer('Date of month'),
|
||||
'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
|
||||
('WE', 'Wednesday'), ('TH', 'Thursday'), \
|
||||
('FR', 'Friday'), ('SA', 'Saturday'), \
|
||||
('SU', 'Sunday')], 'Weekday'),
|
||||
'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
|
||||
('3', 'Third'), ('4', 'Fourth'), \
|
||||
('5', 'Fifth'), ('-1', 'Last')], 'By day'),
|
||||
'week_list': fields.selection([
|
||||
('MO', 'Monday'),
|
||||
('TU', 'Tuesday'),
|
||||
('WE', 'Wednesday'),
|
||||
('TH', 'Thursday'),
|
||||
('FR', 'Friday'),
|
||||
('SA', 'Saturday'),
|
||||
('SU', 'Sunday')], 'Weekday'),
|
||||
'byday': fields.selection([
|
||||
('1', 'First'),
|
||||
('2', 'Second'),
|
||||
('3', 'Third'),
|
||||
('4', 'Fourth'),
|
||||
('5', 'Fifth'),
|
||||
('-1', 'Last')], 'By day'),
|
||||
'month_list': fields.selection(months.items(), 'Month'),
|
||||
'end_date': fields.date('Repeat Until'),
|
||||
'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel', \
|
||||
|
@ -1101,9 +1117,9 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
return res
|
||||
|
||||
_defaults = {
|
||||
'end_type' : 'count',
|
||||
'count' : 1,
|
||||
'rrule_type' : 'none',
|
||||
'end_type': 'count',
|
||||
'count': 1,
|
||||
'rrule_type': False,
|
||||
'state': 'tentative',
|
||||
'class': 'public',
|
||||
'show_as': 'busy',
|
||||
|
@ -1113,7 +1129,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
'user_id': lambda self, cr, uid, ctx: uid,
|
||||
'organizer': default_organizer,
|
||||
}
|
||||
|
||||
|
||||
def _check_closing_date(self, cr, uid, ids, context=None):
|
||||
for event in self.browse(cr, uid, ids, context=context):
|
||||
if event.date_deadline < event.date:
|
||||
|
@ -1129,7 +1145,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
This method gives ids of dates that comes between start date and end date of calendar views
|
||||
@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 limit: The Number of Results to Return """
|
||||
if not context:
|
||||
context = {}
|
||||
|
@ -1140,7 +1156,8 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
result.append(data['id'])
|
||||
continue
|
||||
event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
|
||||
# To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
|
||||
|
||||
# TOCHECK: the start date should be replaced by event date; the event date will be changed by that of calendar code
|
||||
|
||||
if not data['rrule']:
|
||||
continue
|
||||
|
@ -1186,45 +1203,46 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
ids = list(set(result))
|
||||
return ids
|
||||
|
||||
def compute_rule_string(self, datas):
|
||||
def compute_rule_string(self, data):
|
||||
"""
|
||||
Compute rule string according to value type RECUR of iCalendar from the values given.
|
||||
@param self: the object pointer
|
||||
@param datas: dictionary of freq and interval value.
|
||||
@param data: dictionary of freq and interval value
|
||||
@return: string containing recurring rule (empty if no rule)
|
||||
"""
|
||||
def get_week_string(freq, datas):
|
||||
def get_week_string(freq, data):
|
||||
weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
|
||||
if freq == 'weekly':
|
||||
byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
|
||||
byday = map(lambda x: x.upper(), filter(lambda x: data.get(x) and x in weekdays, data))
|
||||
if byday:
|
||||
return ';BYDAY=' + ','.join(byday)
|
||||
return ''
|
||||
|
||||
def get_month_string(freq, datas):
|
||||
def get_month_string(freq, data):
|
||||
if freq == 'monthly':
|
||||
if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
|
||||
if data.get('select1')=='date' and (data.get('day') < 1 or data.get('day') > 31):
|
||||
raise osv.except_osv(_('Error!'), ("Please select a proper day of the month."))
|
||||
|
||||
if datas.get('select1')=='day':
|
||||
return ';BYDAY=' + datas.get('byday') + datas.get('week_list')
|
||||
elif datas.get('select1')=='date':
|
||||
return ';BYMONTHDAY=' + str(datas.get('day'))
|
||||
if data.get('select1')=='day':
|
||||
return ';BYDAY=' + data.get('byday') + data.get('week_list')
|
||||
elif data.get('select1')=='date':
|
||||
return ';BYMONTHDAY=' + str(data.get('day'))
|
||||
return ''
|
||||
|
||||
def get_end_date(datas):
|
||||
if datas.get('end_date'):
|
||||
datas['end_date_new'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
|
||||
def get_end_date(data):
|
||||
if data.get('end_date'):
|
||||
data['end_date_new'] = ''.join((re.compile('\d')).findall(data.get('end_date'))) + 'T235959Z'
|
||||
|
||||
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 '')
|
||||
return (data.get('end_type') == 'count' and (';COUNT=' + str(data.get('count'))) or '') +\
|
||||
((data.get('end_date_new') and data.get('end_type') == 'end_date' and (';UNTIL=' + data.get('end_date_new'))) or '')
|
||||
|
||||
freq=datas.get('rrule_type')
|
||||
if freq == 'none':
|
||||
return ''
|
||||
freq = data.get('rrule_type', False)
|
||||
res = ''
|
||||
if freq:
|
||||
interval_srting = data.get('interval') and (';INTERVAL=' + str(data.get('interval'))) or ''
|
||||
res = 'FREQ=' + freq.upper() + get_week_string(freq, data) + interval_srting + get_end_date(data) + get_month_string(freq, data)
|
||||
|
||||
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)
|
||||
return res
|
||||
|
||||
def _get_empty_rrule_data(self):
|
||||
return {
|
||||
|
@ -1248,19 +1266,6 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
'week_list' : False
|
||||
}
|
||||
|
||||
#def _write_rrule(self, cr, uid, ids, field_value, rule_date=False, context=None):
|
||||
# data = self._get_empty_rrule_data()
|
||||
#
|
||||
# if field_value:
|
||||
# data['recurrency'] = True
|
||||
# for event in self.browse(cr, uid, ids, context=context):
|
||||
# rdate = rule_date or event.date
|
||||
# update_data = self._parse_rrule(field_value, dict(data), rdate)
|
||||
# data.update(update_data)
|
||||
# #parse_rrule
|
||||
# self.write(cr, uid, event.id, data, context=context)
|
||||
|
||||
|
||||
def _parse_rrule(self, rule, data, date_start):
|
||||
day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
|
||||
rrule_type = ['yearly', 'monthly', 'weekly', 'daily']
|
||||
|
@ -1278,7 +1283,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
if i in r._byweekday:
|
||||
data[day_list[i]] = True
|
||||
data['rrule_type'] = 'weekly'
|
||||
#repeat monthly bynweekday ((weekday, weeknumber), )
|
||||
#repeat monthly by nweekday ((weekday, weeknumber), )
|
||||
if r._bynweekday:
|
||||
data['week_list'] = day_list[r._bynweekday[0][0]].upper()
|
||||
data['byday'] = r._bynweekday[0][1]
|
||||
|
@ -1290,7 +1295,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
data['select1'] = 'date'
|
||||
data['rrule_type'] = 'monthly'
|
||||
|
||||
#yearly but for openerp it's monthly, take same information as monthly but interval is 12 times
|
||||
#repeat yearly but for openerp it's monthly, take same information as monthly but interval is 12 times
|
||||
if r._bymonth:
|
||||
data['interval'] = data['interval'] * 12
|
||||
|
||||
|
@ -1363,7 +1368,6 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
except Exception:
|
||||
return True
|
||||
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
|
||||
context = context or {}
|
||||
if isinstance(ids, (str, int, long)):
|
||||
|
@ -1384,14 +1388,14 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
|
||||
'rrule', 'duration', 'exdate'])
|
||||
if data.get('rrule'):
|
||||
data.update(vals)
|
||||
data.update({
|
||||
'recurrent_uid': real_event_id,
|
||||
'recurrent_id': data.get('date'),
|
||||
'rrule_type': 'none',
|
||||
'rrule': '',
|
||||
'recurrency' : False,
|
||||
})
|
||||
data.update(
|
||||
vals,
|
||||
recurrent_uid=real_event_id,
|
||||
recurrent_id=data.get('date'),
|
||||
rrule_type=False,
|
||||
rrule='',
|
||||
recurrency=False,
|
||||
)
|
||||
|
||||
new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
|
||||
|
||||
|
@ -1435,7 +1439,6 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
return res
|
||||
|
||||
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
|
||||
# FIXME This whole id mangling has to go!
|
||||
if context is None:
|
||||
context = {}
|
||||
fields2 = fields and fields[:] or None
|
||||
|
@ -1445,10 +1448,12 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
if fields and (f not in fields):
|
||||
fields2.append(f)
|
||||
|
||||
# FIXME This whole id mangling has to go!
|
||||
if isinstance(ids, (str, int, long)):
|
||||
select = [ids]
|
||||
else:
|
||||
select = ids
|
||||
|
||||
select = map(lambda x: (x, base_calendar_id2real_id(x)), select)
|
||||
result = []
|
||||
|
||||
|
@ -1477,7 +1482,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
if f not in ('id','date','date_deadline','duration','user_id','state'):
|
||||
if isinstance(r[f], list):
|
||||
r[f] = []
|
||||
else:
|
||||
else:
|
||||
r[f] = False
|
||||
if f=='name':
|
||||
r[f] = _('Busy')
|
||||
|
@ -1525,7 +1530,6 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
self.unlink_events(cr, uid, ids, context=context)
|
||||
return res
|
||||
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
@ -1543,7 +1547,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
""" Makes event invitation as Tentative
|
||||
@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 ids: List of Event IDs
|
||||
@param *args: Get Tupple value
|
||||
@param context: A standard dictionary for contextual values
|
||||
|
@ -1554,7 +1558,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
""" Makes event invitation as Tentative
|
||||
@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 ids: List of Event IDs
|
||||
@param *args: Get Tupple value
|
||||
@param context: A standard dictionary for contextual values
|
||||
|
@ -1565,7 +1569,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
""" Makes event invitation as Tentative
|
||||
@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 ids: List of Event IDs
|
||||
@param *args: Get Tupple value
|
||||
@param context: A standard dictionary for contextual values
|
||||
|
@ -1586,9 +1590,9 @@ class calendar_todo(osv.osv):
|
|||
Get Date
|
||||
@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 ids: List of calendar todo's IDs.
|
||||
@param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
|
||||
@param args: list of tuples of form [(‘name_of_the_field', ‘operator', value), ...].
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
|
||||
|
@ -1602,10 +1606,10 @@ class calendar_todo(osv.osv):
|
|||
Set Date
|
||||
@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 id: calendar's ID.
|
||||
@param value: Get Value
|
||||
@param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
|
||||
@param args: list of tuples of form [('name_of_the_field', 'operator', value), ...].
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
|
||||
|
@ -1633,7 +1637,7 @@ class ir_values(osv.osv):
|
|||
Set IR Values
|
||||
@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 model: Get The Model
|
||||
"""
|
||||
|
||||
|
@ -1652,7 +1656,7 @@ class ir_values(osv.osv):
|
|||
Get IR Values
|
||||
@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 model: Get The Model
|
||||
"""
|
||||
if context is None:
|
||||
|
@ -1678,8 +1682,8 @@ class ir_model(osv.osv):
|
|||
Overrides orm read method.
|
||||
@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 IR Model’s IDs.
|
||||
@param uid: the current user's ID for security checks,
|
||||
@param ids: List of IR Model's IDs.
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
new_ids = isinstance(ids, (str, int, long)) and [ids] or ids
|
||||
|
@ -1696,24 +1700,24 @@ ir_model()
|
|||
|
||||
class virtual_report_spool(web_services.report_spool):
|
||||
|
||||
def exp_report(self, db, uid, object, ids, datas=None, context=None):
|
||||
def exp_report(self, db, uid, object, ids, data=None, context=None):
|
||||
"""
|
||||
Export Report
|
||||
@param self: The object pointer
|
||||
@param db: get the current database,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param uid: the current user's ID for security checks,
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
|
||||
if object == 'printscreen.list':
|
||||
return super(virtual_report_spool, self).exp_report(db, uid, \
|
||||
object, ids, datas, context)
|
||||
object, ids, data, context)
|
||||
new_ids = []
|
||||
for id in ids:
|
||||
new_ids.append(base_calendar_id2real_id(id))
|
||||
if datas.get('id', False):
|
||||
datas['id'] = base_calendar_id2real_id(datas['id'])
|
||||
return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context)
|
||||
if data.get('id', False):
|
||||
data['id'] = base_calendar_id2real_id(data['id'])
|
||||
return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, data, context)
|
||||
|
||||
virtual_report_spool()
|
||||
|
||||
|
@ -1725,8 +1729,8 @@ class res_users(osv.osv):
|
|||
Get User Availability
|
||||
@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 res user’s IDs.
|
||||
@param uid: the current user's ID for security checks,
|
||||
@param ids: List of res user's IDs.
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
|
||||
|
@ -1755,8 +1759,8 @@ class res_users(osv.osv):
|
|||
Get User Availability Function
|
||||
@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 res user’s IDs.
|
||||
@param uid: the current user's ID for security checks,
|
||||
@param ids: List of res user's IDs.
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
|
||||
|
@ -1770,5 +1774,4 @@ class res_users(osv.osv):
|
|||
|
||||
res_users()
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- Attendee form view-->
|
||||
|
||||
<!-- Attendee form view -->
|
||||
<record id="base_calendar_attendee_form_view" model="ir.ui.view">
|
||||
<field name="name">calendar.attendee.form</field>
|
||||
<field name="model">calendar.attendee</field>
|
||||
|
@ -55,8 +54,7 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Attendee tree view-->
|
||||
|
||||
<!-- Attendee tree view -->
|
||||
<record id="base_calendar_attendee_tree_view" model="ir.ui.view">
|
||||
<field name="name">calendar.attendee.tree</field>
|
||||
<field name="model">calendar.attendee</field>
|
||||
|
@ -73,8 +71,7 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Attendee search view-->
|
||||
|
||||
<!-- Attendee search view -->
|
||||
<record id="base_calendar_attendee_search_view" model="ir.ui.view">
|
||||
<field name="name">calendar.attendee.search</field>
|
||||
<field name="model">calendar.attendee</field>
|
||||
|
@ -110,18 +107,16 @@
|
|||
<field name="context">{'default_sent_by_uid': uid}</field>
|
||||
</record>
|
||||
|
||||
<!-- Calenadar's menu -->
|
||||
<!-- Calendar's menu -->
|
||||
<menuitem id="base.menu_calendar_configuration" name="Calendar"
|
||||
parent="base.menu_base_config" sequence="50" groups="base.group_no_one"/>
|
||||
|
||||
<!-- Invitation menu -->
|
||||
|
||||
<menuitem id="menu_attendee_invitations"
|
||||
parent="base.menu_calendar_configuration"
|
||||
sequence="10" action="action_view_attendee_form"/>
|
||||
|
||||
<!-- ALARM FORM VIEW-->
|
||||
|
||||
<!-- Alarm form view -->
|
||||
<record id="res_alarm_form_view" model="ir.ui.view">
|
||||
<field name="name">res.alarm.form</field>
|
||||
<field name="model">res.alarm</field>
|
||||
|
@ -140,8 +135,7 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ALARM TREE VIEW-->
|
||||
|
||||
<!-- Alarm list view -->
|
||||
<record id="res_alarm_tree_view" model="ir.ui.view">
|
||||
<field name="name">res.alarm.tree</field>
|
||||
<field name="model">res.alarm</field>
|
||||
|
@ -171,15 +165,13 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu for Alarms-->
|
||||
|
||||
<!-- Alarms menu -->
|
||||
<menuitem id="menu_crm_meeting_avail_alarm"
|
||||
groups="base.group_no_one"
|
||||
action="base_calendar.action_res_alarm_view"
|
||||
parent="base.menu_calendar_configuration" sequence="5"/>
|
||||
|
||||
<!-- Event Form View-->
|
||||
|
||||
<!-- Event form view -->
|
||||
<record model="ir.ui.view" id="event_form_view">
|
||||
<field name="name">Event Form</field>
|
||||
<field name="model">calendar.event</field>
|
||||
|
@ -334,8 +326,7 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Event Tree View -->
|
||||
|
||||
<!-- Event list view -->
|
||||
<record model="ir.ui.view" id="event_tree_view">
|
||||
<field name="name">Event Tree</field>
|
||||
<field name="model">calendar.event</field>
|
||||
|
@ -352,8 +343,7 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Event Calendar View -->
|
||||
|
||||
<!-- Event calendar view -->
|
||||
<record model="ir.ui.view" id="event_calendar_view">
|
||||
<field name="name">Events Calendar</field>
|
||||
<field name="model">calendar.event</field>
|
||||
|
@ -367,8 +357,7 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Event Search View-->
|
||||
|
||||
<!-- Event search view -->
|
||||
<record id="view_calendar_event_filter" model="ir.ui.view">
|
||||
<field name="name">Calendar Events Search</field>
|
||||
<field name="model">calendar.event</field>
|
||||
|
@ -393,8 +382,7 @@
|
|||
</record>
|
||||
|
||||
|
||||
<!-- Event action -->
|
||||
|
||||
<!-- Event action -->
|
||||
<record id="action_view_event" model="ir.actions.act_window">
|
||||
<field name="name">Events</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
|
@ -404,8 +392,7 @@
|
|||
<field name="search_view_id" ref="view_calendar_event_filter"/>
|
||||
</record>
|
||||
|
||||
<!-- Event menu -->
|
||||
|
||||
<!-- Event menu -->
|
||||
<menuitem id="menu_events"
|
||||
name="Events" parent="base.menu_calendar_configuration"
|
||||
sequence="15" action="action_view_event"/>
|
||||
|
|
|
@ -43,14 +43,14 @@ class crm_meeting(base_state, osv.Model):
|
|||
_name = 'crm.meeting'
|
||||
_description = "Meeting"
|
||||
_order = "id desc"
|
||||
_inherit = ["calendar.event", "mail.thread", 'ir.needaction_mixin']
|
||||
_inherit = ["calendar.event", "mail.thread", "ir.needaction_mixin"]
|
||||
_columns = {
|
||||
# base_state required fields
|
||||
'create_date': fields.datetime('Creation Date', readonly=True),
|
||||
'write_date': fields.datetime('Write Date', readonly=True),
|
||||
'date_open': fields.datetime('Confirmed', readonly=True),
|
||||
'date_closed': fields.datetime('Closed', readonly=True),
|
||||
'partner_ids': fields.many2many('res.partner', 'crm_meeting_partner_rel', 'meeting_id','partner_id',
|
||||
'partner_ids': fields.many2many('res.partner', 'crm_meeting_partner_rel', 'meeting_id', 'partner_id',
|
||||
string='Attendees', states={'done': [('readonly', True)]}),
|
||||
'state': fields.selection(
|
||||
[('draft', 'Unconfirmed'), ('open', 'Confirmed')],
|
||||
|
@ -115,5 +115,3 @@ class crm_meeting(base_state, osv.Model):
|
|||
|
||||
def case_close_send_note(self, cr, uid, ids, context=None):
|
||||
return self.message_post(cr, uid, ids, body=_("Meeting <b>completed</b>."), context=context)
|
||||
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
<record id="action_crm_meeting_read" model="ir.values">
|
||||
<field name="name">action_crm_meeting_read</field>
|
||||
<field name="action_id" ref="actions_server_crm_meeting_read"/>
|
||||
<field name="value" eval="'ir.actions.server,' + str(ref('actions_server_crm_meeting_read'))" />
|
||||
<field name="value" eval="'ir.actions.server,' + str(ref('actions_server_crm_meeting_read'))"/>
|
||||
<field name="key">action</field>
|
||||
<field name="model_id" ref="model_crm_meeting" />
|
||||
<field name="model_id" ref="model_crm_meeting"/>
|
||||
<field name="model">crm.meeting</field>
|
||||
<field name="key2">client_action_multi</field>
|
||||
</record>
|
||||
|
@ -33,9 +33,9 @@
|
|||
<record id="action_crm_meeting_unread" model="ir.values">
|
||||
<field name="name">action_crm_meeting_unread</field>
|
||||
<field name="action_id" ref="actions_server_crm_meeting_unread"/>
|
||||
<field name="value" eval="'ir.actions.server,' + str(ref('actions_server_crm_meeting_unread'))" />
|
||||
<field name="value" eval="'ir.actions.server,' + str(ref('actions_server_crm_meeting_unread'))"/>
|
||||
<field name="key">action</field>
|
||||
<field name="model_id" ref="model_crm_meeting" />
|
||||
<field name="model_id" ref="model_crm_meeting"/>
|
||||
<field name="model">crm.meeting</field>
|
||||
<field name="key2">client_action_multi</field>
|
||||
</record>
|
||||
|
@ -73,7 +73,7 @@
|
|||
<sheet>
|
||||
<div class="oe_title">
|
||||
<div class="oe_edit_only">
|
||||
<label for="name"/>
|
||||
<label for="name"/>
|
||||
</div>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
|
@ -116,9 +116,49 @@
|
|||
</page>
|
||||
<page string="Options">
|
||||
<group>
|
||||
<group>
|
||||
<field name="recurrency"
|
||||
attrs="{'readonly': [('recurrent_uid','!=',False)]}"/>
|
||||
<group col="1">
|
||||
<group>
|
||||
<field name="recurrency"
|
||||
attrs="{'readonly': [('recurrent_uid','!=',False)]}"/>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('recurrency','=',False)]}">
|
||||
<label for="interval"/>
|
||||
<div>
|
||||
<field name="interval" attrs="{'required': [('recurrency','==',True)]}" class="oe_inline"/>
|
||||
<field name="rrule_type" attrs="{'required': [('recurrency','==',True)]}" class="oe_inline"/>
|
||||
</div>
|
||||
<label string="Until" for="end_type"/>
|
||||
<div>
|
||||
<field name="end_type" attrs="{'required': [('recurrency','==',True)]}" class="oe_inline"/>
|
||||
<field name="count" attrs="{'invisible': [('end_type', '!=', 'count')], 'required': [('recurrency','==',True)]}" class="oe_inline"/>
|
||||
<field name="end_date" attrs="{'invisible': [('end_type', '!=', 'end_date')], 'required': [('end_type', '=', 'end_date')]}" class="oe_inline"/>
|
||||
</div>
|
||||
<label string="Select Weekdays" attrs="{'invisible' :[('rrule_type','not in', ['weekly'])]}"/>
|
||||
<group col="2" colspan="1" name="weekdays" attrs="{'invisible' :[('rrule_type','not in', ['weekly'])]}">
|
||||
<field name="mo"/>
|
||||
<field name="tu"/>
|
||||
<field name="we"/>
|
||||
<field name="th"/>
|
||||
<field name="fr"/>
|
||||
<field name="sa"/>
|
||||
<field name="su"/>
|
||||
</group>
|
||||
|
||||
<label string="Day of Month"
|
||||
attrs="{'invisible': [('rrule_type','!=','monthly')]}"/>
|
||||
|
||||
<div attrs="{'invisible': [('rrule_type','!=','monthly')]}">
|
||||
<field name="select1"/>
|
||||
<field name="day"
|
||||
attrs="{'required': [('select1','=','date'), ('rrule_type','=','monthly')],
|
||||
'invisible': [('select1','=','day')]}"/>
|
||||
<field name="byday" string="The"
|
||||
attrs="{'required': [('select1','=','day'), ('rrule_type','=','monthly')], 'invisible': [('select1','=','date')]}"/>
|
||||
<field name="week_list" nolabel="1"
|
||||
attrs="{'required': [('select1','=','day'), ('rrule_type','=','monthly')], 'invisible': [('select1','=','date')]}"/>
|
||||
</div>
|
||||
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="alarm_id" widget="selection" groups="base.group_no_one"/>
|
||||
|
@ -128,44 +168,6 @@
|
|||
<field name="recurrent_id" invisible="1"/>
|
||||
<field name="recurrent_uid" invisible="1"/>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('recurrency','=',False)]}">
|
||||
<label for="interval"/>
|
||||
<div>
|
||||
<field name="interval" class="oe_inline"/>
|
||||
<field name="rrule_type" class="oe_inline"/>
|
||||
</div>
|
||||
<label string="Until" for="end_type"/>
|
||||
<div>
|
||||
<field name="end_type" class="oe_inline"/>
|
||||
<field name="count" attrs="{'invisible' : [('end_type', '!=', 'count')] }" class="oe_inline"/>
|
||||
<field name="end_date" attrs="{'invisible' : [('end_type', '!=', 'end_date')], 'required': [('end_type', '=', 'end_date')]}" class="oe_inline"/>
|
||||
</div>
|
||||
<label string="Select Weekdays" attrs="{'invisible' :[('rrule_type','not in', ['weekly'])]}"/>
|
||||
<group col="2" colspan="1" name="weekdays" attrs="{'invisible' :[('rrule_type','not in', ['weekly'])]}">
|
||||
<field name="mo" />
|
||||
<field name="tu" />
|
||||
<field name="we" />
|
||||
<field name="th" />
|
||||
<field name="fr" />
|
||||
<field name="sa" />
|
||||
<field name="su" />
|
||||
</group>
|
||||
|
||||
<label string="Day of Month"
|
||||
attrs="{'invisible' : [('rrule_type','!=','monthly')]}"/>
|
||||
|
||||
<div attrs="{'invisible' : [('rrule_type','!=','monthly')]}">
|
||||
<field name="select1" />
|
||||
<field name="day"
|
||||
attrs="{'required' : [('select1','=','date'), ('rrule_type','=','monthly')],
|
||||
'invisible' : [('select1','=','day')]}" />
|
||||
<field name="byday" string="The"
|
||||
attrs="{'required' : [('select1','=','day'), ('rrule_type','=','monthly')], 'invisible' : [('select1','=','date')]}" />
|
||||
<field name="week_list" nolabel="1"
|
||||
attrs="{'required' : [('select1','=','day'), ('rrule_type','=','monthly')], 'invisible' : [('select1','=','date')]}" />
|
||||
</div>
|
||||
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<!--
|
||||
|
@ -178,12 +180,11 @@
|
|||
<tree string="Invitation details" editable="top">
|
||||
<field name="partner_id"/>
|
||||
<field name="email" string="Mail To"/>
|
||||
<field name="state" />
|
||||
<field name="state"/>
|
||||
<button name="do_tentative"
|
||||
states="needs-action,declined,accepted"
|
||||
string="Uncertain" type="object"
|
||||
icon="terp-crm"
|
||||
/>
|
||||
icon="terp-crm"/>
|
||||
<button name="do_accept" string="Accept"
|
||||
states="needs-action,tentative,declined"
|
||||
type="object" icon="gtk-apply"/>
|
||||
|
@ -195,21 +196,21 @@
|
|||
<header>
|
||||
<button name="do_tentative" type="object"
|
||||
states="needs-action,declined,accepted"
|
||||
string="Uncertain" />
|
||||
string="Uncertain"/>
|
||||
<button name="do_accept" type="object"
|
||||
states="needs-action,tentative,declined"
|
||||
string="Accept" />
|
||||
string="Accept"/>
|
||||
<button name="do_decline" type="object"
|
||||
states="needs-action,tentative,accepted"
|
||||
string="Decline" />
|
||||
string="Decline"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,open,done"/>
|
||||
</header>
|
||||
<group>
|
||||
<group>
|
||||
<field name="email" />
|
||||
<field name="rsvp" />
|
||||
<field name="cutype" />
|
||||
<field name="role" />
|
||||
<field name="email"/>
|
||||
<field name="rsvp"/>
|
||||
<field name="cutype"/>
|
||||
<field name="role"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="partner_id"/>
|
||||
|
@ -237,11 +238,11 @@
|
|||
<field name="model">crm.meeting</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Meetings" fonts="bold:message_unread==True">
|
||||
<field name="name" string="Subject" />
|
||||
<field name="name" string="Subject"/>
|
||||
<field name="user_id"/>
|
||||
<field name="date"/>
|
||||
<field name="state" invisible="True"/>
|
||||
<field name="duration" />
|
||||
<field name="duration"/>
|
||||
<field name="message_unread" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
@ -299,7 +300,7 @@
|
|||
<field name="context">{"calendar_default_user_id": uid}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to schedule a new meeting.
|
||||
Click to schedule a new meeting.
|
||||
</p><p>
|
||||
The calendar is shared between employees and fully integrated with
|
||||
other applications such as the employee holidays or the business
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
duration: 2.5
|
||||
location: OpenERP S.A.
|
||||
name: Technical Presentation
|
||||
rrule_type: none
|
||||
-
|
||||
-
|
||||
Now I will set recurrence for this event to occur monday and friday of week
|
||||
-
|
||||
!python {model: calendar.event}: |
|
||||
|
@ -39,10 +38,9 @@
|
|||
description: 'All day technical test '
|
||||
location: School
|
||||
name: All day test event
|
||||
rrule_type: none
|
||||
-
|
||||
In order to check reminder I will first create reminder
|
||||
-
|
||||
-
|
||||
!record {model: res.alarm, id: res_alarm_daybeforeeventstarts0}:
|
||||
name: 1 Day before event starts
|
||||
trigger_duration: 1
|
||||
|
|
|
@ -140,6 +140,7 @@
|
|||
<field name="duration" widget="float_time"/>
|
||||
<field name="section_id" colspan="1" widget="selection"/>
|
||||
<field name="partner_id" on_change="onchange_partner_id(partner_id)"/>
|
||||
<field name="email_from" invisible="1"/> <!--not needed because of the chatter, thus invisible, but must be in the view as it's returned by onchange_partner_id()-->
|
||||
<field name="categ_id" widget="selection"
|
||||
domain="[('object_id.model', '=', 'crm.phonecall')]"/>
|
||||
<field name="partner_mobile"/>
|
||||
|
|
|
@ -435,22 +435,6 @@ class event_registration(osv.osv):
|
|||
'phone':contact_id.phone,
|
||||
}}
|
||||
|
||||
def onchange_event(self, cr, uid, ids, event_id, context=None):
|
||||
"""This function returns value of Product Name, Unit Price based on Event.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if not event_id:
|
||||
return {}
|
||||
event_obj = self.pool.get('event.event')
|
||||
data_event = event_obj.browse(cr, uid, event_id, context=context)
|
||||
return {'value':
|
||||
{'event_begin_date': data_event.date_begin,
|
||||
'event_end_date': data_event.date_end,
|
||||
'company_id': data_event.company_id and data_event.company_id.id or False,
|
||||
}
|
||||
}
|
||||
|
||||
def onchange_partner_id(self, cr, uid, ids, part, context=None):
|
||||
res_obj = self.pool.get('res.partner')
|
||||
data = {}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
|
||||
<record id="act_event_list_register_event" model="ir.actions.act_window">
|
||||
<field name="res_model">event.registration</field>
|
||||
<field name="view_type">form</field>
|
||||
|
@ -136,10 +136,22 @@
|
|||
<field name="type" on_change="onchange_event_type(type,context)"/>
|
||||
<field name="date_begin"/>
|
||||
<field name="date_end"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</group>
|
||||
</div>
|
||||
<notebook>
|
||||
<page string="Email Configuration" groups="base.group_no_one">
|
||||
<group>
|
||||
<field name="reply_to"/>
|
||||
<group>
|
||||
<field name="email_registration_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="email_confirmation_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Event Description">
|
||||
<field name="note" colspan="4" nolabel="1"/>
|
||||
</page>
|
||||
|
@ -217,7 +229,7 @@
|
|||
<field name="register_current"/>
|
||||
<field name="register_min"/>
|
||||
<field name="register_max" invisible="1"/>
|
||||
<field name="main_speaker_id" groups="base.extended"/>
|
||||
<field name="main_speaker_id" groups="base.group_no_one"/>
|
||||
<field name="user_id"/>
|
||||
<field name="state"/>
|
||||
<field name="message_unread" invisible="1"/>
|
||||
|
@ -452,7 +464,7 @@
|
|||
<sheet string="Registration">
|
||||
<label for="event_id" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="event_id" on_change="onchange_event(event_id, context)" domain="[('state','in',('draft','confirm'))]"/>
|
||||
<field name="event_id" domain="[('state','in',('draft','confirm'))]"/>
|
||||
</h1>
|
||||
<group>
|
||||
<group>
|
||||
|
|
|
@ -62,8 +62,8 @@
|
|||
<newline/>
|
||||
<group expand="1" string="Group By...">
|
||||
<filter string="Participant / Contact" icon="terp-personal" context="{'group_by':'name_registration'}" help="Registration contact"/>
|
||||
<filter string="Register" icon="terp-personal" context="{'group_by':'user_id_registration'}" help="Registration contact" groups="base.extended"/>
|
||||
<filter string="Speaker" name="speaker" icon="terp-personal+" context="{'group_by': 'speaker_id'}" groups="base.extended"/>
|
||||
<filter string="Register" icon="terp-personal" context="{'group_by':'user_id_registration'}" help="Registration contact" groups="base.group_no_one"/>
|
||||
<filter string="Speaker" name="speaker" icon="terp-personal+" context="{'group_by': 'speaker_id'}" groups="base.group_no_one"/>
|
||||
<filter string="Event Responsible" name="user_id" icon="terp-personal" context="{'group_by': 'user_id'}"/>
|
||||
<filter string="Event" name="event" icon="terp-crm" context="{'group_by':'event_id', 'max_reg_event_visible':0}"/>
|
||||
<filter string="Event Type" icon="terp-crm" context="{'group_by':'event_type'}"/>
|
||||
|
|
|
@ -212,13 +212,6 @@ class hr_holidays(osv.osv):
|
|||
}
|
||||
return result
|
||||
|
||||
def onchange_status_id(self, cr, uid, ids, status_id, context=None):
|
||||
double_validation = False
|
||||
if status_id:
|
||||
holiday_status = self.pool.get('hr.holidays.status').browse(cr, uid, status_id, context=context)
|
||||
double_validation = holiday_status.double_validation
|
||||
return {'value': {'double_validation': double_validation}}
|
||||
|
||||
def set_to_draft(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {
|
||||
'state': 'draft',
|
||||
|
|
|
@ -100,7 +100,7 @@
|
|||
<group>
|
||||
<group>
|
||||
<field name="name" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')]}"/>
|
||||
<field name="holiday_status_id" on_change="onchange_status_id(holiday_status_id)" context="{'employee_id':employee_id}"/>
|
||||
<field name="holiday_status_id" context="{'employee_id':employee_id}"/>
|
||||
<label for="number_of_days_temp" string="Duration"/>
|
||||
<div>
|
||||
<group col="3">
|
||||
|
@ -145,7 +145,7 @@
|
|||
<group>
|
||||
<group>
|
||||
<field name="name" required="1" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')]}"/>
|
||||
<field name="holiday_status_id" on_change="onchange_status_id(holiday_status_id)" context="{'employee_id':employee_id}"/>
|
||||
<field name="holiday_status_id" context="{'employee_id':employee_id}"/>
|
||||
<field name="number_of_days_temp"/>
|
||||
</group>
|
||||
<group>
|
||||
|
|
|
@ -65,5 +65,8 @@ The validation can be configured in the company:
|
|||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': True,
|
||||
'js': ['static/src/js/timesheet.js',],
|
||||
'css': ['static/src/css/timesheet.css',],
|
||||
'qweb': ['static/src/xml/timesheet.xml',],
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -27,80 +27,6 @@ from osv import fields, osv
|
|||
from tools.translate import _
|
||||
import netsvc
|
||||
|
||||
class one2many_mod2(fields.one2many):
|
||||
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
if values is None:
|
||||
values = {}
|
||||
|
||||
# res6 = {id: date_current, ...}
|
||||
res6 = dict([(rec['id'], rec['date_current'])
|
||||
for rec in obj.read(cr, user, ids, ['date_current'], context=context)])
|
||||
|
||||
dom = []
|
||||
for c, id in enumerate(ids):
|
||||
if id in res6:
|
||||
if c: # skip first
|
||||
dom.insert(0 ,'|')
|
||||
dom.append('&')
|
||||
dom.append('&')
|
||||
dom.append(('name', '>=', res6[id]))
|
||||
dom.append(('name', '<=', res6[id]))
|
||||
dom.append(('sheet_id', '=', id))
|
||||
|
||||
ids2 = obj.pool.get(self._obj).search(cr, user, dom, limit=self._limit)
|
||||
|
||||
res = {}
|
||||
for i in ids:
|
||||
res[i] = []
|
||||
|
||||
for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_read'):
|
||||
if r[self._fields_id]:
|
||||
res[r[self._fields_id][0]].append(r['id'])
|
||||
return res
|
||||
|
||||
def set(self, cr, obj, id, field, values, user=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
context = context.copy()
|
||||
context['sheet_id'] = id
|
||||
return super(one2many_mod2, self).set(cr, obj, id, field, values, user=user, context=context)
|
||||
|
||||
|
||||
class one2many_mod(fields.one2many):
|
||||
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
if values is None:
|
||||
values = {}
|
||||
|
||||
|
||||
res5 = obj.read(cr, user, ids, ['date_current'], context=context)
|
||||
res6 = {}
|
||||
for r in res5:
|
||||
res6[r['id']] = r['date_current']
|
||||
|
||||
ids2 = []
|
||||
for id in ids:
|
||||
dom = []
|
||||
if id in res6:
|
||||
dom = [('date', '=', res6[id]), ('sheet_id', '=', id)]
|
||||
ids2.extend(obj.pool.get(self._obj).search(cr, user,
|
||||
dom, limit=self._limit))
|
||||
res = {}
|
||||
for i in ids:
|
||||
res[i] = []
|
||||
for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2,
|
||||
[self._fields_id], context=context, load='_classic_read'):
|
||||
if r[self._fields_id]:
|
||||
res[r[self._fields_id][0]].append(r['id'])
|
||||
|
||||
return res
|
||||
|
||||
class hr_timesheet_sheet(osv.osv):
|
||||
_name = "hr_timesheet_sheet.sheet"
|
||||
_inherit = "mail.thread"
|
||||
|
@ -111,8 +37,7 @@ class hr_timesheet_sheet(osv.osv):
|
|||
def _total_attendances(self, cr, uid, ids, name, args, context=None):
|
||||
""" Get the total attendance for the timesheets
|
||||
Returns a dict like :
|
||||
{id: {'date_current': '2011-06-17',
|
||||
'total_per_day': {day: timedelta, ...},
|
||||
{id: {'total_per_day': {day: timedelta, ...},
|
||||
},
|
||||
...
|
||||
}
|
||||
|
@ -122,7 +47,6 @@ class hr_timesheet_sheet(osv.osv):
|
|||
res = {}
|
||||
for sheet_id in ids:
|
||||
sheet = self.browse(cr, uid, sheet_id, context=context)
|
||||
date_current = sheet.date_current
|
||||
# field attendances_ids of hr_timesheet_sheet.sheet only
|
||||
# returns attendances of timesheet's current date
|
||||
attendance_ids = attendance_obj.search(cr, uid, [('sheet_id', '=', sheet_id)], context=context)
|
||||
|
@ -143,20 +67,7 @@ class hr_timesheet_sheet(osv.osv):
|
|||
else:
|
||||
total_attendance[day] += attendance_interval
|
||||
|
||||
# if the delta is negative, it means that a sign out is missing
|
||||
# in a such case, we want to have the time to the end of the day
|
||||
# for a past date, and the time to now for the current date
|
||||
if total_attendance[day] < timedelta(0):
|
||||
if day == date_current:
|
||||
now = datetime.now()
|
||||
total_attendance[day] += timedelta(hours=now.hour,
|
||||
minutes=now.minute,
|
||||
seconds=now.second)
|
||||
else:
|
||||
total_attendance[day] += timedelta(days=1)
|
||||
|
||||
res[sheet_id] = {'date_current': date_current,
|
||||
'total_per_day': total_attendance}
|
||||
res[sheet_id] = {'total_per_day': total_attendance}
|
||||
return res
|
||||
|
||||
def _total_timesheet(self, cr, uid, ids, name, args, context=None):
|
||||
|
@ -210,24 +121,16 @@ class hr_timesheet_sheet(osv.osv):
|
|||
|
||||
all_attendances_sheet = all_timesheet_attendances[id]
|
||||
|
||||
date_current = all_attendances_sheet['date_current']
|
||||
total_attendances_sheet = all_attendances_sheet['total_per_day']
|
||||
total_attendances_all_days = sum_all_days(total_attendances_sheet)
|
||||
total_attendances_day = total_attendances_sheet.get(date_current, timedelta(seconds=0))
|
||||
|
||||
total_timesheets_sheet = all_timesheet_lines[id]
|
||||
total_timesheets_all_days = sum_all_days(total_timesheets_sheet)
|
||||
total_timesheets_day = total_timesheets_sheet.get(date_current, timedelta(seconds=0))
|
||||
total_difference_all_days = total_attendances_all_days - total_timesheets_all_days
|
||||
total_difference_day = total_attendances_day - total_timesheets_day
|
||||
|
||||
res[id]['total_attendance'] = timedelta_to_hours(total_attendances_all_days)
|
||||
res[id]['total_timesheet'] = timedelta_to_hours(total_timesheets_all_days)
|
||||
res[id]['total_difference'] = timedelta_to_hours(total_difference_all_days)
|
||||
|
||||
res[id]['total_attendance_day'] = timedelta_to_hours(total_attendances_day)
|
||||
res[id]['total_timesheet_day'] = timedelta_to_hours(total_timesheets_day)
|
||||
res[id]['total_difference_day'] = timedelta_to_hours(total_difference_day)
|
||||
return res
|
||||
|
||||
def check_employee_attendance_state(self, cr, uid, sheet_id, context=None):
|
||||
|
@ -277,44 +180,6 @@ class hr_timesheet_sheet(osv.osv):
|
|||
raise osv.except_osv(_('Warning!'), _('Please verify that the total difference of the sheet is lower than %.2f.') %(di,))
|
||||
return True
|
||||
|
||||
def date_today(self, cr, uid, ids, context=None):
|
||||
for sheet in self.browse(cr, uid, ids, context=context):
|
||||
if datetime.today() <= datetime.strptime(sheet.date_from, '%Y-%m-%d'):
|
||||
self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from,}, context=context)
|
||||
elif datetime.now() >= datetime.strptime(sheet.date_to, '%Y-%m-%d'):
|
||||
self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to,}, context=context)
|
||||
else:
|
||||
self.write(cr, uid, [sheet.id], {'date_current': time.strftime('%Y-%m-%d')}, context=context)
|
||||
return True
|
||||
|
||||
def date_previous(self, cr, uid, ids, context=None):
|
||||
for sheet in self.browse(cr, uid, ids, context=context):
|
||||
if datetime.strptime(sheet.date_current, '%Y-%m-%d') <= datetime.strptime(sheet.date_from, '%Y-%m-%d'):
|
||||
self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from,}, context=context)
|
||||
else:
|
||||
self.write(cr, uid, [sheet.id], {
|
||||
'date_current': (datetime.strptime(sheet.date_current, '%Y-%m-%d') + relativedelta(days=-1)).strftime('%Y-%m-%d'),
|
||||
}, context=context)
|
||||
return True
|
||||
|
||||
def date_next(self, cr, uid, ids, context=None):
|
||||
for sheet in self.browse(cr, uid, ids, context=context):
|
||||
if datetime.strptime(sheet.date_current, '%Y-%m-%d') >= datetime.strptime(sheet.date_to, '%Y-%m-%d'):
|
||||
self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to,}, context=context)
|
||||
else:
|
||||
self.write(cr, uid, [sheet.id], {
|
||||
'date_current': (datetime.strptime(sheet.date_current, '%Y-%m-%d') + relativedelta(days=1)).strftime('%Y-%m-%d'),
|
||||
}, context=context)
|
||||
return True
|
||||
|
||||
def button_dummy(self, cr, uid, ids, context=None):
|
||||
for sheet in self.browse(cr, uid, ids, context=context):
|
||||
if datetime.strptime(sheet.date_current, '%Y-%m-%d') <= datetime.strptime(sheet.date_from, '%Y-%m-%d'):
|
||||
self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from,}, context=context)
|
||||
elif datetime.strptime(sheet.date_current, '%Y-%m-%d') >= datetime.strptime(sheet.date_to, '%Y-%m-%d'):
|
||||
self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to,}, context=context)
|
||||
return True
|
||||
|
||||
def attendance_action_change(self, cr, uid, ids, context=None):
|
||||
hr_employee = self.pool.get('hr.employee')
|
||||
employee_ids = []
|
||||
|
@ -329,14 +194,13 @@ class hr_timesheet_sheet(osv.osv):
|
|||
'user_id': fields.related('employee_id', 'user_id', type="many2one", relation="res.users", store=True, string="User", required=False, readonly=True),#fields.many2one('res.users', 'User', required=True, select=1, states={'confirm':[('readonly', True)], 'done':[('readonly', True)]}),
|
||||
'date_from': fields.date('Date from', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}),
|
||||
'date_to': fields.date('Date to', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}),
|
||||
'date_current': fields.date('Current date', required=True, select=1),
|
||||
'timesheet_ids' : one2many_mod('hr.analytic.timesheet', 'sheet_id',
|
||||
'Timesheet lines', domain=[('date', '=', time.strftime('%Y-%m-%d'))],
|
||||
'timesheet_ids' : fields.one2many('hr.analytic.timesheet', 'sheet_id',
|
||||
'Timesheet lines',
|
||||
readonly=True, states={
|
||||
'draft': [('readonly', False)],
|
||||
'new': [('readonly', False)]}
|
||||
),
|
||||
'attendances_ids' : one2many_mod2('hr.attendance', 'sheet_id', 'Attendances'),
|
||||
'attendances_ids' : fields.one2many('hr.attendance', 'sheet_id', 'Attendances'),
|
||||
'state' : fields.selection([
|
||||
('new', 'New'),
|
||||
('draft','Open'),
|
||||
|
@ -346,9 +210,6 @@ class hr_timesheet_sheet(osv.osv):
|
|||
\n* The \'Confirmed\' state is used for to confirm the timesheet by user. \
|
||||
\n* The \'Done\' state is used when users timesheet is accepted by his/her senior.'),
|
||||
'state_attendance' : fields.related('employee_id', 'state', type='selection', selection=[('absent', 'Absent'), ('present', 'Present')], string='Current Status', readonly=True),
|
||||
'total_attendance_day': fields.function(_total, method=True, string='Total Attendance', multi="_total"),
|
||||
'total_timesheet_day': fields.function(_total, method=True, string='Total Timesheet', multi="_total"),
|
||||
'total_difference_day': fields.function(_total, method=True, string='Difference', multi="_total"),
|
||||
'total_attendance': fields.function(_total, method=True, string='Total Attendance', multi="_total"),
|
||||
'total_timesheet': fields.function(_total, method=True, string='Total Timesheet', multi="_total"),
|
||||
'total_difference': fields.function(_total, method=True, string='Difference', multi="_total"),
|
||||
|
@ -386,7 +247,6 @@ class hr_timesheet_sheet(osv.osv):
|
|||
|
||||
_defaults = {
|
||||
'date_from' : _default_date_from,
|
||||
'date_current' : lambda *a: time.strftime('%Y-%m-%d'),
|
||||
'date_to' : _default_date_to,
|
||||
'state': 'new',
|
||||
'employee_id': _default_employee,
|
||||
|
@ -406,16 +266,9 @@ class hr_timesheet_sheet(osv.osv):
|
|||
return False
|
||||
return True
|
||||
|
||||
def _date_current_check(self, cr, uid, ids, context=None):
|
||||
for sheet in self.browse(cr, uid, ids, context=context):
|
||||
if sheet.date_current < sheet.date_from or sheet.date_current > sheet.date_to:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
_constraints = [
|
||||
(_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']),
|
||||
]
|
||||
|
||||
def action_set_to_draft(self, cr, uid, ids, *args):
|
||||
|
@ -498,7 +351,7 @@ class hr_timesheet_line(osv.osv):
|
|||
|
||||
_columns = {
|
||||
'sheet_id': fields.function(_sheet, string='Sheet',
|
||||
type='many2one', relation='hr_timesheet_sheet.sheet',
|
||||
type='many2one', relation='hr_timesheet_sheet.sheet', ondelete="cascade",
|
||||
store={
|
||||
'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10),
|
||||
'account.analytic.line': (_get_account_analytic_line, ['user_id', 'date'], 10),
|
||||
|
@ -534,6 +387,10 @@ class hr_timesheet_line(osv.osv):
|
|||
raise osv.except_osv(_('Error!'), _('You cannot modify an entry in a confirmed timesheet.'))
|
||||
return True
|
||||
|
||||
def multi_on_change_account_id(self, cr, uid, ids, account_ids, context=None):
|
||||
return dict([(el, self.on_change_account_id(cr, uid, ids, el)) for el in account_ids])
|
||||
|
||||
|
||||
hr_timesheet_line()
|
||||
|
||||
class hr_attendance(osv.osv):
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
<field name="name">Sheet 1</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="employee_id" ref="hr.employee_fp" />
|
||||
<field eval="time.strftime('%Y-%m-%d')" name="date_current"/>
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
|
|
|
@ -71,20 +71,13 @@
|
|||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Weekly">
|
||||
<widget type="weekly_timesheet">
|
||||
</widget>
|
||||
</page>
|
||||
<page string="Daily">
|
||||
<group>
|
||||
<div>
|
||||
<button name="button_dummy" class="oe_inline" string="Go to" type="object" icon="terp-gtk-jump-to-ltr"/> :
|
||||
<field name="date_current" class="oe_inline"/>
|
||||
</div>
|
||||
<div align="right">
|
||||
<button class="oe_inline" icon="terp-gtk-go-back-ltr" name="date_previous" string="" type="object"/>
|
||||
<button class="oe_inline" name="date_today" string="Today" type="object" icon="terp-go-today"/>
|
||||
<button class="oe_inline" icon="terp-gtk-go-back-rtl" name="date_next" string="" type="object"/>
|
||||
</div>
|
||||
</group>
|
||||
<group colspan="4" col="3">
|
||||
<field context="{'name':date_current,'user_id':user_id}" name="attendances_ids" nolabel="1" groups="base.group_hr_attendance">
|
||||
<field context="{'user_id':user_id}" name="attendances_ids" nolabel="1" groups="base.group_hr_attendance">
|
||||
<tree string="Attendances" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="action"/>
|
||||
|
@ -101,9 +94,9 @@
|
|||
<group col="4">
|
||||
<field name="state_attendance" groups="base.group_hr_attendance"/>
|
||||
</group>
|
||||
<field colspan="4" context="{'date':date_current,'user_id':user_id}" domain="[('name','=',date_current)]" name="timesheet_ids" nolabel="1">
|
||||
<field colspan="4" context="{'user_id':user_id}" name="timesheet_ids" nolabel="1">
|
||||
<tree editable="top" string="Timesheet Lines">
|
||||
<field invisible="1" name="date"/>
|
||||
<field name="date"/>
|
||||
<field domain="[('type','in',['normal', 'contract']), ('state', '<>', 'close'),('use_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id)" context="{'default_use_timesheets': 1}"/>
|
||||
<field name="name"/>
|
||||
<field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time"/>
|
||||
|
|
|
@ -46,7 +46,6 @@ class timesheet_report(osv.osv):
|
|||
'department_id':fields.many2one('hr.department','Department',readonly=True),
|
||||
'date_from': fields.date('Date from',readonly=True,),
|
||||
'date_to': fields.date('Date to',readonly=True),
|
||||
'date_current': fields.date('Current date', required=True),
|
||||
'state' : fields.selection([
|
||||
('new', 'New'),
|
||||
('draft','Draft'),
|
||||
|
@ -62,13 +61,9 @@ class timesheet_report(osv.osv):
|
|||
create or replace view timesheet_report as (
|
||||
select
|
||||
min(aal.id) as id,
|
||||
htss.date_current,
|
||||
htss.name,
|
||||
htss.date_from,
|
||||
htss.date_to,
|
||||
to_char(htss.date_current,'YYYY') as year,
|
||||
to_char(htss.date_current,'MM') as month,
|
||||
to_char(htss.date_current, 'YYYY-MM-DD') as day,
|
||||
count(*) as nbr,
|
||||
aal.unit_amount as quantity,
|
||||
aal.amount as cost,
|
||||
|
@ -77,18 +72,15 @@ class timesheet_report(osv.osv):
|
|||
(SELECT sum(day.total_difference)
|
||||
FROM hr_timesheet_sheet_sheet AS sheet
|
||||
LEFT JOIN hr_timesheet_sheet_sheet_day AS day
|
||||
ON (sheet.id = day.sheet_id
|
||||
AND day.name = sheet.date_current) where sheet.id=htss.id) as total_diff,
|
||||
ON (sheet.id = day.sheet_id) where sheet.id=htss.id) as total_diff,
|
||||
(SELECT sum(day.total_timesheet)
|
||||
FROM hr_timesheet_sheet_sheet AS sheet
|
||||
LEFT JOIN hr_timesheet_sheet_sheet_day AS day
|
||||
ON (sheet.id = day.sheet_id
|
||||
AND day.name = sheet.date_current) where sheet.id=htss.id) as total_timesheet,
|
||||
ON (sheet.id = day.sheet_id) where sheet.id=htss.id) as total_timesheet,
|
||||
(SELECT sum(day.total_attendance)
|
||||
FROM hr_timesheet_sheet_sheet AS sheet
|
||||
LEFT JOIN hr_timesheet_sheet_sheet_day AS day
|
||||
ON (sheet.id = day.sheet_id
|
||||
AND day.name = sheet.date_current) where sheet.id=htss.id) as total_attendance,
|
||||
ON (sheet.id = day.sheet_id) where sheet.id=htss.id) as total_attendance,
|
||||
aal.to_invoice,
|
||||
aal.general_account_id,
|
||||
htss.user_id,
|
||||
|
@ -99,15 +91,11 @@ class timesheet_report(osv.osv):
|
|||
left join hr_analytic_timesheet as hat ON (hat.line_id=aal.id)
|
||||
left join hr_timesheet_sheet_sheet as htss ON (hat.line_id=htss.id)
|
||||
group by
|
||||
to_char(htss.date_current,'YYYY'),
|
||||
to_char(htss.date_current,'MM'),
|
||||
to_char(htss.date_current, 'YYYY-MM-DD'),
|
||||
aal.account_id,
|
||||
htss.date_from,
|
||||
htss.date_to,
|
||||
aal.unit_amount,
|
||||
aal.amount,
|
||||
htss.date_current,
|
||||
aal.to_invoice,
|
||||
aal.product_id,
|
||||
aal.general_account_id,
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
<field name="model">timesheet.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree colors="blue:state == 'draft';black:state in ('confirm','new');gray:state == 'cancel'" string="Timesheet">
|
||||
<field name="date_current" invisible="1"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="date_from" invisible="1"/>
|
||||
|
@ -56,7 +55,6 @@
|
|||
<field name="product_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="date_current"/>
|
||||
<field name="date_to"/>
|
||||
<field name="date_from"/>
|
||||
</group>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
|
||||
.openerp .oe_timesheet_weekly {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly td {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly th {
|
||||
text-align: right;
|
||||
color: #069;
|
||||
font-family: 'Helvetica Neue', Arial, Verdana, 'Nimbus Sans L', sans-serif;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly th.oe_timesheet_weekly_date_head {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly td {
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_account {
|
||||
text-align: left;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly td input.oe_timesheet_weekly_input {
|
||||
border: 1px solid #CCC;
|
||||
padding: 5px 2px !important;
|
||||
color: #666 !important;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
width: 38px;
|
||||
text-align: right;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly td .oe_timesheet_weekly_box {
|
||||
padding: 5px 2px !important;
|
||||
color: #666 !important;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
width: 38px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_adding_tot {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_adding {
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_tottot {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_add_row td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_add_row .oe_form_field_many2one {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly_today {
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
|
||||
openerp.hr_timesheet_sheet = function(instance) {
|
||||
var QWeb = instance.web.qweb;
|
||||
var _t = instance.web._t;
|
||||
|
||||
instance.hr_timesheet_sheet.WeeklyTimesheet = instance.web.form.FormWidget.extend(instance.web.form.ReinitializeWidgetMixin, {
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.set({
|
||||
sheets: [],
|
||||
date_to: false,
|
||||
date_from: false,
|
||||
});
|
||||
this.field_manager.on("field_changed:timesheet_ids", this, this.query_sheets);
|
||||
this.field_manager.on("field_changed:date_from", this, function() {
|
||||
this.set({"date_from": instance.web.str_to_date(this.field_manager.get_field_value("date_from"))});
|
||||
});
|
||||
this.field_manager.on("field_changed:date_to", this, function() {
|
||||
this.set({"date_to": instance.web.str_to_date(this.field_manager.get_field_value("date_to"))});
|
||||
});
|
||||
this.field_manager.on("field_changed:user_id", this, function() {
|
||||
this.set({"user_id": this.field_manager.get_field_value("user_id")});
|
||||
});
|
||||
this.on("change:sheets", this, this.update_sheets);
|
||||
this.res_o2m_drop = new instance.web.DropMisordered();
|
||||
this.render_drop = new instance.web.DropMisordered();
|
||||
this.description_line = _t("/");
|
||||
},
|
||||
query_sheets: function() {
|
||||
var self = this;
|
||||
if (self.updating)
|
||||
return;
|
||||
var commands = this.field_manager.get_field_value("timesheet_ids");
|
||||
this.res_o2m_drop.add(new instance.web.Model(this.view.model).call("resolve_2many_commands", ["timesheet_ids", commands, [],
|
||||
new instance.web.CompoundContext()]))
|
||||
.then(function(result) {
|
||||
self.querying = true;
|
||||
self.set({sheets: result});
|
||||
self.querying = false;
|
||||
});
|
||||
},
|
||||
update_sheets: function() {
|
||||
var self = this;
|
||||
if (self.querying)
|
||||
return;
|
||||
self.updating = true;
|
||||
self.field_manager.set_values({timesheet_ids: self.get("sheets")}).then(function() {
|
||||
self.updating = false;
|
||||
});
|
||||
},
|
||||
initialize_field: function() {
|
||||
instance.web.form.ReinitializeWidgetMixin.initialize_field.call(this);
|
||||
var self = this;
|
||||
self.on("change:sheets", self, self.initialize_content);
|
||||
self.on("change:date_to", self, self.initialize_content);
|
||||
self.on("change:date_from", self, self.initialize_content);
|
||||
self.on("change:user_id", self, self.initialize_content);
|
||||
},
|
||||
initialize_content: function() {
|
||||
var self = this;
|
||||
if (self.setting)
|
||||
return;
|
||||
// don't render anything until we have date_to and date_from
|
||||
if (!self.get("date_to") || !self.get("date_from"))
|
||||
return;
|
||||
this.destroy_content();
|
||||
|
||||
// it's important to use those vars to avoid race conditions
|
||||
var dates;
|
||||
var accounts;
|
||||
var account_names;
|
||||
var default_get;
|
||||
return this.render_drop.add(new instance.web.Model("hr.analytic.timesheet").call("default_get", [
|
||||
['account_id','general_account_id', 'journal_id','date','name','user_id','product_id','product_uom_id','to_invoice','amount','unit_amount'],
|
||||
new instance.web.CompoundContext({'user_id': self.get('user_id')})]).pipe(function(result) {
|
||||
default_get = result;
|
||||
// calculating dates
|
||||
dates = [];
|
||||
var start = self.get("date_from");
|
||||
var end = self.get("date_to");
|
||||
while (start <= end) {
|
||||
dates.push(start);
|
||||
start = start.clone().addDays(1);
|
||||
}
|
||||
// group by account
|
||||
accounts = _(self.get("sheets")).chain()
|
||||
.map(function(el) {
|
||||
// much simpler to use only the id in all cases
|
||||
if (typeof(el.account_id) === "object")
|
||||
el.account_id = el.account_id[0];
|
||||
return el;
|
||||
})
|
||||
.groupBy("account_id").value();
|
||||
|
||||
var account_ids = _.map(_.keys(accounts), function(el) { return el === "false" ? false : Number(el) });
|
||||
|
||||
return new instance.web.Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], account_ids,
|
||||
new instance.web.CompoundContext({'user_id': self.get('user_id')})]).pipe(function(accounts_defaults) {
|
||||
accounts = _(accounts).chain().map(function(lines, account_id) {
|
||||
account_defaults = _.extend({}, default_get, accounts_defaults[account_id]);
|
||||
// group by days
|
||||
account_id = account_id === "false" ? false : Number(account_id);
|
||||
var index = _.groupBy(lines, "date");
|
||||
var days = _.map(dates, function(date) {
|
||||
var day = {day: date, lines: index[instance.web.date_to_str(date)] || []};
|
||||
// add line where we will insert/remove hours
|
||||
var to_add = _.find(day.lines, function(line) { return line.name === self.description_line });
|
||||
if (to_add) {
|
||||
day.lines = _.without(day.lines, to_add);
|
||||
day.lines.unshift(to_add);
|
||||
} else {
|
||||
day.lines.unshift(_.extend(_.clone(account_defaults), {
|
||||
name: self.description_line,
|
||||
unit_amount: 0,
|
||||
date: instance.web.date_to_str(date),
|
||||
account_id: account_id,
|
||||
}));
|
||||
}
|
||||
return day;
|
||||
});
|
||||
return {account: account_id, days: days, account_defaults: account_defaults};
|
||||
}).value();
|
||||
|
||||
// we need the name_get of the analytic accounts
|
||||
return new instance.web.Model("account.analytic.account").call("name_get", [_.pluck(accounts, "account"),
|
||||
new instance.web.CompoundContext()]).pipe(function(result) {
|
||||
account_names = {};
|
||||
_.each(result, function(el) {
|
||||
account_names[el[0]] = el[1];
|
||||
});
|
||||
accounts = _.sortBy(accounts, function(el) {
|
||||
return account_names[el.account];
|
||||
});
|
||||
});;
|
||||
});
|
||||
})).pipe(function(result) {
|
||||
// we put all the gathered data in self, then we render
|
||||
self.dates = dates;
|
||||
self.accounts = accounts;
|
||||
self.account_names = account_names;
|
||||
self.default_get = default_get;
|
||||
//real rendering
|
||||
self.display_data();
|
||||
});
|
||||
},
|
||||
destroy_content: function() {
|
||||
if (this.dfm) {
|
||||
this.dfm.destroy();
|
||||
this.dfm = undefined;
|
||||
}
|
||||
},
|
||||
display_data: function() {
|
||||
var self = this;
|
||||
self.$el.html(QWeb.render("hr_timesheet_sheet.WeeklyTimesheet", {widget: self}));
|
||||
_.each(self.accounts, function(account) {
|
||||
_.each(_.range(account.days.length), function(day_count) {
|
||||
if (!self.get('effective_readonly')) {
|
||||
self.get_box(account, day_count).val(self.sum_box(account, day_count)).change(function() {
|
||||
var num = Number($(this).val());
|
||||
if (isNaN(num)) {
|
||||
$(this).val(self.sum_box(account, day_count));
|
||||
} else {
|
||||
account.days[day_count].lines[0].unit_amount += num - self.sum_box(account, day_count);
|
||||
self.display_totals();
|
||||
self.sync();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.get_box(account, day_count).html(self.sum_box(account, day_count));
|
||||
}
|
||||
});
|
||||
});
|
||||
self.display_totals();
|
||||
self.$(".oe_timesheet_weekly_adding button").click(_.bind(this.init_add_account, this));
|
||||
},
|
||||
init_add_account: function() {
|
||||
var self = this;
|
||||
if (self.dfm)
|
||||
return;
|
||||
self.$(".oe_timesheet_weekly_add_row").show();
|
||||
self.dfm = new instance.web.form.DefaultFieldManager(self);
|
||||
self.dfm.extend_field_desc({
|
||||
account: {
|
||||
relation: "account.analytic.account",
|
||||
},
|
||||
});
|
||||
self.account_m2o = new instance.web.form.FieldMany2One(self.dfm, {
|
||||
attrs: {
|
||||
name: "account",
|
||||
type: "many2one",
|
||||
domain: [
|
||||
['type','in',['normal', 'contract']],
|
||||
['state', '<>', 'close'],
|
||||
['use_timesheets','=',1],
|
||||
['id', 'not in', _.pluck(self.accounts, "account")],
|
||||
],
|
||||
modifiers: '{"required": true}',
|
||||
},
|
||||
});
|
||||
self.account_m2o.prependTo(self.$(".oe_timesheet_weekly_add_row td"));
|
||||
self.$(".oe_timesheet_weekly_add_row button").click(function() {
|
||||
var id = self.account_m2o.get_value();
|
||||
if (id === false) {
|
||||
self.dfm.set({display_invalid_fields: true});
|
||||
return;
|
||||
}
|
||||
var ops = self.generate_o2m_value();
|
||||
new instance.web.Model("hr.analytic.timesheet").call("on_change_account_id", [[], id]).pipe(function(res) {
|
||||
var def = _.extend({}, self.default_get, res.value, {
|
||||
name: self.description_line,
|
||||
unit_amount: 0,
|
||||
date: instance.web.date_to_str(self.dates[0]),
|
||||
account_id: id,
|
||||
});
|
||||
ops.push(def);
|
||||
self.set({"sheets": ops});
|
||||
});
|
||||
});
|
||||
},
|
||||
get_box: function(account, day_count) {
|
||||
return this.$('[data-account="' + account.account + '"][data-day-count="' + day_count + '"]');
|
||||
},
|
||||
get_total: function(account) {
|
||||
return this.$('[data-account-total="' + account.account + '"]');
|
||||
},
|
||||
get_day_total: function(day_count) {
|
||||
return this.$('[data-day-total="' + day_count + '"]');
|
||||
},
|
||||
get_super_total: function() {
|
||||
return this.$('.oe_timesheet_weekly_supertotal');
|
||||
},
|
||||
sum_box: function(account, day_count) {
|
||||
var line_total = 0;
|
||||
_.each(account.days[day_count].lines, function(line) {
|
||||
line_total += line.unit_amount;
|
||||
});
|
||||
return line_total;
|
||||
},
|
||||
display_totals: function() {
|
||||
var self = this;
|
||||
var day_tots = _.map(_.range(self.dates.length), function() { return 0 });
|
||||
var super_tot = 0;
|
||||
_.each(self.accounts, function(account) {
|
||||
var acc_tot = 0;
|
||||
_.each(_.range(self.dates.length), function(day_count) {
|
||||
var sum = self.sum_box(account, day_count);
|
||||
acc_tot += sum;
|
||||
day_tots[day_count] += sum;
|
||||
super_tot += sum;
|
||||
});
|
||||
self.get_total(account).html(acc_tot);
|
||||
});
|
||||
_.each(_.range(self.dates.length), function(day_count) {
|
||||
self.get_day_total(day_count).html(day_tots[day_count]);
|
||||
});
|
||||
self.get_super_total().html(super_tot);
|
||||
},
|
||||
sync: function() {
|
||||
var self = this;
|
||||
self.setting = true;
|
||||
self.set({sheets: this.generate_o2m_value()});
|
||||
self.setting = false;
|
||||
},
|
||||
generate_o2m_value: function() {
|
||||
var self = this;
|
||||
var ops = [];
|
||||
|
||||
_.each(self.accounts, function(account) {
|
||||
var auth_keys = _.extend(_.clone(account.account_defaults), {
|
||||
name: true, unit_amount: true, date: true, account_id:true,
|
||||
});
|
||||
_.each(account.days, function(day) {
|
||||
_.each(day.lines, function(line) {
|
||||
if (line.unit_amount !== 0) {
|
||||
var tmp = _.clone(line);
|
||||
tmp.id = undefined;
|
||||
_.each(line, function(v, k) {
|
||||
if (v instanceof Array) {
|
||||
tmp[k] = v[0];
|
||||
}
|
||||
});
|
||||
// we have to remove some keys, because analytic lines are shitty
|
||||
_.each(_.keys(tmp), function(key) {
|
||||
if (auth_keys[key] === undefined) {
|
||||
tmp[key] = undefined;
|
||||
}
|
||||
});
|
||||
ops.push(tmp);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
return ops;
|
||||
},
|
||||
});
|
||||
|
||||
instance.web.form.custom_widgets.add('weekly_timesheet', 'instance.hr_timesheet_sheet.WeeklyTimesheet');
|
||||
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<templates>
|
||||
<t t-name="hr_timesheet_sheet.WeeklyTimesheet">
|
||||
<div class="oe_timesheet_weekly">
|
||||
<table>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<t t-foreach="widget.dates" t-as="date">
|
||||
<th t-att-class="'oe_timesheet_weekly_date_head' + (Date.compare(date, Date.today()) === 0 ? ' oe_timesheet_weekly_today' : '')">
|
||||
<t t-esc="date.toString('ddd')"/><br />
|
||||
<t t-esc="date.toString('MMM d')"/>
|
||||
</th>
|
||||
</t>
|
||||
<th class="oe_timesheet_weekly_date_head">TOTAL</th>
|
||||
</tr>
|
||||
<tr t-foreach="widget.accounts" t-as="account">
|
||||
<td class="oe_timesheet_weekly_account"><t t-esc="widget.account_names[account.account]"/></td>
|
||||
<t t-set="day_count" t-value="0"/>
|
||||
<t t-foreach="account.days" t-as="day">
|
||||
<td t-att-class="(Date.compare(day.day, Date.today()) === 0 ? 'oe_timesheet_weekly_today' : '')">
|
||||
<input t-if="!widget.get('effective_readonly')" class="oe_timesheet_weekly_input" t-att-data-account="account.account"
|
||||
t-att-data-day-count="day_count" type="text"/>
|
||||
<span t-if="widget.get('effective_readonly')" t-att-data-account="account.account"
|
||||
t-att-data-day-count="day_count" class="oe_timesheet_weekly_box"/>
|
||||
<t t-set="day_count" t-value="day_count + 1"/>
|
||||
</td>
|
||||
</t>
|
||||
<td t-att-data-account-total="account.account"> </td>
|
||||
</tr>
|
||||
<tr class="oe_timesheet_weekly_add_row" style="display: none">
|
||||
<td t-att-colspan="widget.dates.length + 2">
|
||||
<button>Add</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="oe_timesheet_weekly_adding_tot">
|
||||
<div class="oe_timesheet_weekly_adding"><button>Add Row</button></div>
|
||||
<div class="oe_timesheet_weekly_tottot"><span>TOTAL</span></div>
|
||||
</div>
|
||||
</td>
|
||||
<t t-set="day_count" t-value="0"/>
|
||||
<t t-foreach="widget.dates" t-as="date">
|
||||
<td t-att-class="(Date.compare(date, Date.today()) === 0 ? 'oe_timesheet_weekly_today' : '')">
|
||||
<span class="oe_timesheet_weekly_box" t-att-data-day-total="day_count">
|
||||
</span>
|
||||
<t t-set="day_count" t-value="day_count + 1"/>
|
||||
</td>
|
||||
</t>
|
||||
<td class="oe_timesheet_weekly_supertotal"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
|
@ -16,7 +16,6 @@
|
|||
I create a timesheet for employee "Quentin Paolinon".
|
||||
-
|
||||
!record {model: hr_timesheet_sheet.sheet, id: hr_timesheet_sheet_sheet_deddk0}:
|
||||
date_current: !eval time.strftime('%Y-%m-%d')
|
||||
date_from: !eval time.strftime('%Y-%m-01')
|
||||
name: Quentin Paolinon
|
||||
state: new
|
||||
|
@ -34,30 +33,6 @@
|
|||
-
|
||||
!assert {model: hr.employee, id: hr.employee_qdp}:
|
||||
- state == 'present'
|
||||
-
|
||||
I want to check attendance and work of yesterday. I click on <- button.
|
||||
-
|
||||
!python {model: hr_timesheet_sheet.sheet}: |
|
||||
date_prev = self.date_previous(cr, uid, [ref('hr_timesheet_sheet_sheet_deddk0')], None)
|
||||
assert date_prev == True, "I See Previous Date Timesheet"
|
||||
-
|
||||
Then I click on "Today" button to fill today's timesheet.
|
||||
-
|
||||
!python {model: hr_timesheet_sheet.sheet}: |
|
||||
date_to = self.date_today(cr, uid, [ref('hr_timesheet_sheet_sheet_deddk0')], None)
|
||||
assert date_to == True, "I See Today Date Timesheet"
|
||||
-
|
||||
I can also move to next day by clicking on -> button.
|
||||
-
|
||||
!python {model: hr_timesheet_sheet.sheet}: |
|
||||
date_next = self.date_next(cr, uid, [ref('hr_timesheet_sheet_sheet_deddk0')], None)
|
||||
assert date_next == True, "I See Next Date Timesheet"
|
||||
-
|
||||
I want to go to a particular date and see attendance then I select the date and click on "Go to:" button.
|
||||
-
|
||||
!python {model: hr_timesheet_sheet.sheet}: |
|
||||
button_dumy = self.button_dummy(cr, uid, [ref('hr_timesheet_sheet_sheet_deddk0')], None)
|
||||
assert button_dumy == True, "I See Particular Date Attendance"
|
||||
-
|
||||
At the time of logout, I create attendance and perform "Sign Out".
|
||||
-
|
||||
|
|
|
@ -42,7 +42,6 @@ class hr_timesheet_current_open(osv.osv_memory):
|
|||
view_type = 'tree,form'
|
||||
domain = "[('id','in',["+','.join(map(str, ids))+"]),('user_id', '=', uid)]"
|
||||
elif len(ids)==1:
|
||||
ts.write(cr, uid, ids, {'date_current': time.strftime('%Y-%m-%d')}, context=context)
|
||||
domain = "[('user_id', '=', uid)]"
|
||||
else:
|
||||
domain = "[('user_id', '=', uid)]"
|
||||
|
|
|
@ -33,6 +33,7 @@ import report
|
|||
import wizard
|
||||
import res_config
|
||||
import mail_group_menu
|
||||
import update
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -13,6 +13,20 @@
|
|||
<field eval="'()'" name="args"/>
|
||||
</record>
|
||||
|
||||
<record id="ir_cron_module_update_notification" model="ir.cron">
|
||||
<field name="name">Update Notification</field>
|
||||
<field eval="True" name="active" />
|
||||
<field name="user_id" ref="base.user_root" />
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">weeks</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall" />
|
||||
<field eval="'ir.module.module'" name="model" />
|
||||
<field eval="'update_notification'" name="function" />
|
||||
<field eval="'(None,)'" name="args" />
|
||||
<field name="priority">1000</field>
|
||||
</record>
|
||||
|
||||
<record id="mt_comment" model="mail.message.subtype">
|
||||
<field name="name">comment</field>
|
||||
</record>
|
||||
|
|
|
@ -504,7 +504,7 @@ class mail_message(osv.Model):
|
|||
if not values.get('message_id') and values.get('res_id') and values.get('model'):
|
||||
values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s' % values)
|
||||
newid = super(mail_message, self).create(cr, uid, values, context)
|
||||
self._notify(cr, 1, newid, context=context)
|
||||
self._notify(cr, SUPERUSER_ID, newid, context=context)
|
||||
return newid
|
||||
|
||||
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
|
||||
|
|
|
@ -39,63 +39,6 @@ _logger = logging.getLogger(__name__)
|
|||
def decode_header(message, header, separator=' '):
|
||||
return separator.join(map(decode, message.get_all(header, [])))
|
||||
|
||||
class many2many_reference(fields.many2many):
|
||||
""" many2many_reference manages many2many fields where one id is found
|
||||
by a reference-like key (a char column in addition to the foreign id).
|
||||
The reference_column attribute on the many2many fields is used;
|
||||
if not defined, ``res_model`` is used. """
|
||||
|
||||
def _get_query_and_where_params(self, cr, model, ids, values, where_params):
|
||||
""" Add in where condition like mail_followers.res_model = 'crm.lead' """
|
||||
reference_column = self.reference_column if self.reference_column else 'res_model'
|
||||
values.update(reference_column=reference_column, reference_value=model._name)
|
||||
query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
|
||||
FROM %(rel)s, %(from_c)s \
|
||||
WHERE %(rel)s.%(id1)s IN %%s \
|
||||
AND %(rel)s.%(id2)s = %(tbl)s.id \
|
||||
AND %(rel)s.%(reference_column)s = \'%(reference_value)s\' \
|
||||
%(where_c)s \
|
||||
%(order_by)s \
|
||||
%(limit)s \
|
||||
OFFSET %(offset)d' \
|
||||
% values
|
||||
return query, where_params
|
||||
|
||||
def set(self, cr, model, id, name, values, user=None, context=None):
|
||||
""" Override to add the reference field in queries. """
|
||||
if not values: return
|
||||
rel, id1, id2 = self._sql_names(model)
|
||||
obj = model.pool.get(self._obj)
|
||||
# reference column name: given by attribute or res_model
|
||||
reference_column = self.reference_column if self.reference_column else 'res_model'
|
||||
for act in values:
|
||||
if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
|
||||
continue
|
||||
if act[0] == 0:
|
||||
idnew = obj.create(cr, user, act[2], context=context)
|
||||
cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, idnew, model._name))
|
||||
elif act[0] == 3:
|
||||
cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND '+reference_column+'=%s', (id, act[1], model._name))
|
||||
elif act[0] == 4:
|
||||
# following queries are in the same transaction - so should be relatively safe
|
||||
cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND '+reference_column+'=%s', (id, act[1], model._name))
|
||||
if not cr.fetchone():
|
||||
cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, act[1], model._name))
|
||||
elif act[0] == 5:
|
||||
cr.execute('delete from '+rel+' where '+id1+' = %s AND '+reference_column+'=%s', (id, model._name))
|
||||
elif act[0] == 6:
|
||||
d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
|
||||
if d1:
|
||||
d1 = ' and ' + ' and '.join(d1)
|
||||
else:
|
||||
d1 = ''
|
||||
cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+reference_column+'=%s AND '+id2+' IN (SELECT '+rel+'.'+id2+' FROM '+rel+', '+','.join(tables)+' WHERE '+rel+'.'+id1+'=%s AND '+rel+'.'+id2+' = '+obj._table+'.id '+ d1 +')', [id, model._name, id]+d2)
|
||||
for act_nbr in act[2]:
|
||||
cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, act_nbr, model._name))
|
||||
# cases 1, 2: performs write and unlink -> default implementation is ok
|
||||
else:
|
||||
return super(many2many_reference, self).set(cr, model, id, name, values, user, context)
|
||||
|
||||
|
||||
class mail_thread(osv.AbstractModel):
|
||||
''' mail_thread model is meant to be inherited by any model that needs to
|
||||
|
@ -186,6 +129,62 @@ class mail_thread(osv.AbstractModel):
|
|||
res[notif.message_id.res_id] = True
|
||||
return [('id', 'in', res.keys())]
|
||||
|
||||
def _get_followers(self, cr, uid, ids, name, arg, context=None):
|
||||
fol_obj = self.pool.get('mail.followers')
|
||||
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)])
|
||||
res = dict((res_id, []) for res_id in ids)
|
||||
for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids):
|
||||
res[fol.res_id].append(fol.partner_id.id)
|
||||
return res
|
||||
|
||||
def _set_followers(self, cr, uid, id, name, value, arg, context=None):
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
fol_obj = self.pool.get('mail.followers')
|
||||
|
||||
# read the old set of followers, and determine the new set of followers
|
||||
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', '=', id)])
|
||||
old = set(fol.partner_id.id for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids))
|
||||
new = set(old)
|
||||
|
||||
for command in value:
|
||||
if isinstance(command, (int, long)):
|
||||
new.add(command)
|
||||
elif command[0] == 0:
|
||||
new.add(partner_obj.create(cr, uid, command[2], context=context))
|
||||
elif command[0] == 1:
|
||||
partner_obj.write(cr, uid, [command[1]], command[2], context=context)
|
||||
new.add(command[1])
|
||||
elif command[0] == 2:
|
||||
partner_obj.unlink(cr, uid, [command[1]], context=context)
|
||||
new.discard(command[1])
|
||||
elif command[0] == 3:
|
||||
new.discard(command[1])
|
||||
elif command[0] == 4:
|
||||
new.add(command[1])
|
||||
elif command[0] == 5:
|
||||
new.clear()
|
||||
elif command[0] == 6:
|
||||
new = set(command[2])
|
||||
|
||||
# remove partners that are no longer followers
|
||||
fol_ids = fol_obj.search(cr, SUPERUSER_ID,
|
||||
[('res_model', '=', self._name), ('res_id', '=', id), ('partner_id', 'not in', list(new))])
|
||||
fol_obj.unlink(cr, SUPERUSER_ID, fol_ids)
|
||||
|
||||
# add new followers
|
||||
for partner_id in new - old:
|
||||
fol_obj.create(cr, SUPERUSER_ID, {'res_model': self._name, 'res_id': id, 'partner_id': partner_id})
|
||||
|
||||
def _search_followers(self, cr, uid, obj, name, args, context):
|
||||
fol_obj = self.pool.get('mail.followers')
|
||||
res = []
|
||||
for field, operator, value in args:
|
||||
assert field == name
|
||||
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('partner_id', operator, value)])
|
||||
res_ids = [fol.res_id for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids)]
|
||||
res.append(('id', 'in', res_ids))
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'message_is_follower': fields.function(_get_subscription_data,
|
||||
type='boolean', string='Is a Follower', multi='_get_subscription_data,'),
|
||||
|
@ -194,9 +193,8 @@ class mail_thread(osv.AbstractModel):
|
|||
help="Holds data about the subtypes. The content of this field "\
|
||||
"is a structure holding the current model subtypes, and the "\
|
||||
"current document followed subtypes."),
|
||||
'message_follower_ids': many2many_reference('res.partner',
|
||||
'mail_followers', 'res_id', 'partner_id',
|
||||
reference_column='res_model', string='Followers'),
|
||||
'message_follower_ids': fields.function(_get_followers, fnct_inv=_set_followers,
|
||||
fnct_search=_search_followers, type='many2many', obj='res.partner', string='Followers'),
|
||||
'message_comment_ids': fields.one2many('mail.message', 'res_id',
|
||||
domain=lambda self: [('model', '=', self._name), ('type', 'in', ('comment', 'email'))],
|
||||
string='Comments and emails',
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<field name="name">Mail.group: access only public and joined groups</field>
|
||||
<field name="model_id" ref="model_mail_group"/>
|
||||
<!-- This rule has to be improved for employee only groups -->
|
||||
<field name="domain_force">['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.id]), '&', ('public','=','groups'), ('group_public_id','in', [x.id for x in user.groups_id])]</field>
|
||||
<field name="domain_force">['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.partner_id.id]), '&', ('public','=','groups'), ('group_public_id','in', [g.id for g in user.groups_id])]</field>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
|
|
|
@ -149,7 +149,6 @@ openerp_mail_followers = function(session, mail) {
|
|||
var self = this;
|
||||
var node_user_list = this.$('ul.oe_mail_followers_display').empty();
|
||||
this.$('div.oe_mail_recthread_followers h4').html(this.options.title + (records.length>=5 ? ' (' + records.length + ')' : '') );
|
||||
console.log(records);
|
||||
for(var i=0; i<records.length&&i<5; i++) {
|
||||
var record=records[i];
|
||||
record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
|
||||
|
|
|
@ -194,7 +194,7 @@ class test_mail(TestMailMockups):
|
|||
'plaintext mail incorrectly parsed')
|
||||
|
||||
def test_10_many2many_reference_field(self):
|
||||
""" Tests designed for the many2many_reference field (follower_ids).
|
||||
""" Tests designed for the many2many function field 'follower_ids'.
|
||||
We will test to perform writes using the many2many commands 0, 3, 4,
|
||||
5 and 6. """
|
||||
cr, uid = self.cr, self.uid
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
import pooler
|
||||
import release
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
from tools.safe_eval import safe_eval
|
||||
from tools.config import config
|
||||
from tools import misc
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
Time interval that will be used to determine up to which date we will
|
||||
check the logs to see if a message we just received was already logged.
|
||||
@type: datetime.timedelta
|
||||
"""
|
||||
_PREVIOUS_LOG_CHECK = datetime.timedelta(days=365)
|
||||
|
||||
def get_sys_logs(cr, uid):
|
||||
"""
|
||||
Utility method to send a publisher warranty get logs messages.
|
||||
"""
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
|
||||
dbuuid = pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid')
|
||||
db_create_date = pool.get('ir.config_parameter').get_param(cr, uid, 'database.create_date')
|
||||
limit_date = datetime.datetime.now()
|
||||
limit_date = limit_date - datetime.timedelta(15)
|
||||
limit_date_str = limit_date.strftime(misc.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
nbr_users = pool.get("res.users").search(cr, uid, [], count=True)
|
||||
nbr_active_users = pool.get("res.users").search(cr, uid, [("date", ">=", limit_date_str)], count=True)
|
||||
nbr_share_users = False
|
||||
nbr_active_share_users = False
|
||||
if "share" in pool.get("res.users")._all_columns:
|
||||
nbr_share_users = pool.get("res.users").search(cr, uid, [("share", "=", True)], count=True)
|
||||
nbr_active_share_users = pool.get("res.users").search(cr, uid, [("share", "=", True), ("date", ">=", limit_date_str)], count=True)
|
||||
user = pool.get("res.users").browse(cr, uid, uid)
|
||||
msg = {
|
||||
"dbuuid": dbuuid,
|
||||
"nbr_users": nbr_users,
|
||||
"nbr_active_users": nbr_active_users,
|
||||
"nbr_share_users": nbr_share_users,
|
||||
"nbr_active_share_users": nbr_active_share_users,
|
||||
"dbname": cr.dbname,
|
||||
"db_create_date": db_create_date,
|
||||
"version": release.version,
|
||||
"language": user.lang,
|
||||
}
|
||||
msg.update(pool.get("res.company").read(cr,uid,[1],["name","email","phone"])[0])
|
||||
|
||||
add_arg = {"timeout":30} if sys.version_info >= (2,6) else {}
|
||||
arguments = {'arg0': msg, "action": "update",}
|
||||
arguments_raw = urllib.urlencode(arguments)
|
||||
|
||||
url = config.get("publisher_warranty_url")
|
||||
uo = urllib2.urlopen(url, arguments_raw, **add_arg)
|
||||
result = {}
|
||||
try:
|
||||
submit_result = uo.read()
|
||||
result = safe_eval(submit_result)
|
||||
finally:
|
||||
uo.close()
|
||||
return result
|
||||
|
||||
class publisher_warranty_contract(osv.osv):
|
||||
_name = "publisher_warranty.contract"
|
||||
|
||||
def update_notification(self, cr, uid, ids, cron_mode=True, context=None):
|
||||
"""
|
||||
Send a message to OpenERP's publisher warranty server to check the
|
||||
validity of the contracts, get notifications, etc...
|
||||
|
||||
@param cron_mode: If true, catch all exceptions (appropriate for usage in a cron).
|
||||
@type cron_mode: boolean
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
result = get_sys_logs(cr, uid)
|
||||
except Exception:
|
||||
if cron_mode: # we don't want to see any stack trace in cron
|
||||
return False
|
||||
_logger.debug("Exception while sending a get logs messages", exc_info=1)
|
||||
raise osv.except_osv(_("Error"), _("Error during communication with the publisher warranty server."))
|
||||
limit_date = (datetime.datetime.now() - _PREVIOUS_LOG_CHECK).strftime(misc.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
# old behavior based on res.log; now on mail.message, that is not necessarily installed
|
||||
proxy = self.pool.get('mail.message')
|
||||
|
||||
model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'group_all_employees')
|
||||
|
||||
for message in result["messages"]:
|
||||
values = {
|
||||
'body' : message,
|
||||
'model' : 'mail.group',
|
||||
'res_id' : res_id,
|
||||
'user_id' : False,
|
||||
}
|
||||
proxy.create(cr, uid, values, context=context)
|
||||
except Exception:
|
||||
if cron_mode:
|
||||
return False # we don't want to see any stack trace in cron
|
||||
else:
|
||||
raise
|
||||
return True
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
@ -121,7 +121,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
'partner_ids': lambda self, cr, uid, ctx={}: [],
|
||||
}
|
||||
|
||||
def notify(self, cr, uid, newid, context=None):
|
||||
def _notify(self, cr, uid, newid, context=None):
|
||||
""" Override specific notify method of mail.message, because we do
|
||||
not want that feature in the wizard. """
|
||||
return
|
||||
|
|
|
@ -8,19 +8,19 @@ msgstr ""
|
|||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-02-08 00:36+0000\n"
|
||||
"PO-Revision-Date: 2012-01-12 05:49+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"PO-Revision-Date: 2012-10-08 15:06+0000\n"
|
||||
"Last-Translator: kifcaliph <Unknown>\n"
|
||||
"Language-Team: Arabic <ar@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-28 06:38+0000\n"
|
||||
"X-Generator: Launchpad (build 15864)\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-09 04:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16112)\n"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign:0
|
||||
msgid "Manual Mode"
|
||||
msgstr ""
|
||||
msgstr "النمط اليدوي"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: field:marketing.campaign.transition,activity_from_id:0
|
||||
|
@ -31,12 +31,12 @@ msgstr "النشاط السابق"
|
|||
#: code:addons/marketing_campaign/marketing_campaign.py:818
|
||||
#, python-format
|
||||
msgid "The current step for this item has no email or report to preview."
|
||||
msgstr ""
|
||||
msgstr "الخطوة الحالية لهذا الصنف لا تملك بريد إلكتروني أو تقرير للمعاينة"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: constraint:marketing.campaign.transition:0
|
||||
msgid "The To/From Activity of transition must be of the same Campaign "
|
||||
msgstr ""
|
||||
msgstr "الإنتقال من / إلى النشاط يجب أن يكون من نفس الحملة "
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: selection:marketing.campaign.transition,trigger:0
|
||||
|
@ -46,7 +46,7 @@ msgstr "الوقت"
|
|||
#. module: marketing_campaign
|
||||
#: selection:marketing.campaign.activity,type:0
|
||||
msgid "Custom Action"
|
||||
msgstr ""
|
||||
msgstr "تخصيص إجراء"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:campaign.analysis:0
|
||||
|
@ -63,11 +63,13 @@ msgid ""
|
|||
"reached this point has generated a certain revenue. You can get revenue "
|
||||
"statistics in the Reporting section"
|
||||
msgstr ""
|
||||
"تعيين الإيرادات المتوقعة إذا كنت ترى أن كل بند من بنود الحملة وصلت الى تلك "
|
||||
"النقطة وولدت عائد معين. يمكنك الحصول على إحصاءات الإيرادات في قسم التقارير"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: field:marketing.campaign.transition,trigger:0
|
||||
msgid "Trigger"
|
||||
msgstr ""
|
||||
msgstr "زر"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: field:campaign.analysis,count:0
|
||||
|
@ -77,7 +79,7 @@ msgstr ""
|
|||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign:0
|
||||
msgid "Campaign Editor"
|
||||
msgstr ""
|
||||
msgstr "محرر الحملة"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:campaign.analysis:0
|
||||
|
@ -106,13 +108,13 @@ msgstr "كائن"
|
|||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign.segment:0
|
||||
msgid "Sync mode: only records created after last sync"
|
||||
msgstr ""
|
||||
msgstr "نمط المزامنة: السجلات فقط التي تم إنشاؤها بعد آخر المزامنة"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: model:email.template,body_text:marketing_campaign.email_template_2
|
||||
msgid ""
|
||||
"Hello, We are happy to announce that you now become our Silver Partner."
|
||||
msgstr ""
|
||||
msgstr "مرحبا، ونحن سعداء أن نعلن أنك أصبحت الآن شريكاً فضياً لدينا."
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign:0
|
||||
|
@ -123,7 +125,7 @@ msgstr "حفظ كمسودة"
|
|||
#. module: marketing_campaign
|
||||
#: field:marketing.campaign.activity,to_ids:0
|
||||
msgid "Next Activities"
|
||||
msgstr ""
|
||||
msgstr "النشاطات التالية"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign.segment:0
|
||||
|
@ -133,7 +135,7 @@ msgstr "مزامنة"
|
|||
#. module: marketing_campaign
|
||||
#: sql_constraint:marketing.campaign.transition:0
|
||||
msgid "The interval must be positive or zero"
|
||||
msgstr ""
|
||||
msgstr "الفترة يجب أن تكون إيجابية أو صفر"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: code:addons/marketing_campaign/marketing_campaign.py:818
|
||||
|
@ -145,7 +147,7 @@ msgstr "لا معاينة"
|
|||
#: view:marketing.campaign.segment:0
|
||||
#: field:marketing.campaign.segment,date_run:0
|
||||
msgid "Launch Date"
|
||||
msgstr ""
|
||||
msgstr "تاريخ الإنشاء"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:campaign.analysis:0
|
||||
|
@ -156,7 +158,7 @@ msgstr "يوم"
|
|||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign.activity:0
|
||||
msgid "Outgoing Transitions"
|
||||
msgstr ""
|
||||
msgstr "التنقلات الصادرة"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign.workitem:0
|
||||
|
@ -166,18 +168,18 @@ msgstr "إستعادة"
|
|||
#. module: marketing_campaign
|
||||
#: help:marketing.campaign,object_id:0
|
||||
msgid "Choose the resource on which you want this campaign to be run"
|
||||
msgstr ""
|
||||
msgstr "اختيار المورد الذي تريده ليتم تشغيله لهذه الحملة"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: field:marketing.campaign.segment,sync_last_date:0
|
||||
msgid "Last Synchronization"
|
||||
msgstr ""
|
||||
msgstr "التزامن الأخير"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: code:addons/marketing_campaign/marketing_campaign.py:214
|
||||
#, python-format
|
||||
msgid "You can not duplicate a campaign, it's not supported yet."
|
||||
msgstr ""
|
||||
msgstr "لا يمكنك تكرار الحملة، انها غير مدعومة حتى الآن."
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: selection:marketing.campaign.transition,interval_type:0
|
||||
|
@ -189,7 +191,7 @@ msgstr "سنة/سنين"
|
|||
msgid ""
|
||||
"Date on which this segment was synchronized last time (automatically or "
|
||||
"manually)"
|
||||
msgstr ""
|
||||
msgstr "التاريخ الذي تزامن هذا القطاع آخر مرة (آليا أو يدويا)"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: selection:campaign.analysis,state:0
|
||||
|
@ -216,11 +218,19 @@ msgid ""
|
|||
"Normal - the campaign runs normally and automatically sends all emails and "
|
||||
"reports (be very careful with this mode, you're live!)"
|
||||
msgstr ""
|
||||
"اختبار - وهو يخلق ويعالج كافة الأنشطة مباشرة (دون انتظار تأخير على التحولات) "
|
||||
"ولكن لا يرسل رسائل البريد الإلكتروني أو يعد التقارير.\n"
|
||||
" اختبار في الوقت الحقيقي - وهو يخلق ويعالج جميع الأنشطة بشكل مباشر ولكن لا "
|
||||
"يرسل رسائل البريد الإلكتروني أو يعد التقارير.\n"
|
||||
" مع التأكيد اليدوي - يعمل الحملات عادة، ولكن المستخدم لديه للتحقق من صحة كل "
|
||||
"بنود العمل يدويا.\n"
|
||||
" طبيعي - يعمل الحملة بشكل طبيعي يرسل تلقائيا جميع رسائل البريد الإلكتروني "
|
||||
"والتقارير (نكون حذرين للغاية مع هذا الوضع، أنت على الهواء!)"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: help:marketing.campaign.segment,date_run:0
|
||||
msgid "Initial start date of this segment."
|
||||
msgstr ""
|
||||
msgstr "تاريخ بدء الأولي لهذا القطاع."
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:campaign.analysis:0
|
||||
|
@ -237,7 +247,7 @@ msgstr "حملة"
|
|||
#. module: marketing_campaign
|
||||
#: model:email.template,subject:marketing_campaign.email_template_3
|
||||
msgid "Congratulation! You become our Gold Partner."
|
||||
msgstr ""
|
||||
msgstr "تهنئة! لقد أصبحت شريكاً ذهبياً الآن."
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:campaign.analysis:0
|
||||
|
@ -251,7 +261,7 @@ msgstr "قطعة"
|
|||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign.activity:0
|
||||
msgid "Cost / Revenue"
|
||||
msgstr ""
|
||||
msgstr "التكلفة/العائد"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: help:marketing.campaign.activity,type:0
|
||||
|
@ -313,7 +323,7 @@ msgstr ""
|
|||
#. module: marketing_campaign
|
||||
#: view:campaign.analysis:0
|
||||
msgid "Marketing Reports"
|
||||
msgstr ""
|
||||
msgstr "تقارير التسويق"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: selection:marketing.campaign,state:0
|
||||
|
@ -342,7 +352,7 @@ msgstr ""
|
|||
#. module: marketing_campaign
|
||||
#: field:marketing.campaign.segment,sync_mode:0
|
||||
msgid "Synchronization mode"
|
||||
msgstr ""
|
||||
msgstr "نمط التزامن"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign:0
|
||||
|
@ -353,7 +363,7 @@ msgstr ""
|
|||
#. module: marketing_campaign
|
||||
#: field:marketing.campaign.activity,from_ids:0
|
||||
msgid "Previous Activities"
|
||||
msgstr ""
|
||||
msgstr "النشاطات السابقة"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: help:marketing.campaign.segment,date_done:0
|
||||
|
@ -363,7 +373,7 @@ msgstr ""
|
|||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign.workitem:0
|
||||
msgid "Marketing Campaign Activities"
|
||||
msgstr ""
|
||||
msgstr "نشاطات حملة التسويق"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign.workitem:0
|
||||
|
@ -427,7 +437,7 @@ msgstr ""
|
|||
#. module: marketing_campaign
|
||||
#: model:ir.model,name:marketing_campaign.model_marketing_campaign_segment
|
||||
msgid "Campaign Segment"
|
||||
msgstr ""
|
||||
msgstr "حملة القطاع"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign:0
|
||||
|
@ -456,7 +466,7 @@ msgstr ""
|
|||
#. module: marketing_campaign
|
||||
#: field:marketing.campaign,fixed_cost:0
|
||||
msgid "Fixed Cost"
|
||||
msgstr ""
|
||||
msgstr "التكلفة الثابتة"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: model:email.template,subject:marketing_campaign.email_template_2
|
||||
|
@ -466,12 +476,12 @@ msgstr ""
|
|||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign.segment:0
|
||||
msgid "Newly Modified"
|
||||
msgstr ""
|
||||
msgstr "تم التعديل حديثاً"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: field:marketing.campaign.transition,interval_nbr:0
|
||||
msgid "Interval Value"
|
||||
msgstr ""
|
||||
msgstr "قيمة الفترة"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: field:campaign.analysis,revenue:0
|
||||
|
@ -512,22 +522,22 @@ msgstr ""
|
|||
#. module: marketing_campaign
|
||||
#: model:ir.actions.act_window,name:marketing_campaign.act_marketing_campaing_followup
|
||||
msgid "Campaign Follow-up"
|
||||
msgstr ""
|
||||
msgstr "متابعة الحملة"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: help:marketing.campaign.activity,email_template_id:0
|
||||
msgid "The e-mail to send when this activity is activated"
|
||||
msgstr ""
|
||||
msgstr "بريد إلكتروني للإرسال عندما يتم تنشيط المنشط"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: view:marketing.campaign:0
|
||||
msgid "Test Mode"
|
||||
msgstr ""
|
||||
msgstr "نمط الإختبار"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: selection:marketing.campaign.segment,sync_mode:0
|
||||
msgid "Only records modified after last sync (no duplicates)"
|
||||
msgstr ""
|
||||
msgstr "فقط السجلات المعدلة بعد آخر مزامنة (بدون تكرار)"
|
||||
|
||||
#. module: marketing_campaign
|
||||
#: model:ir.model,name:marketing_campaign.model_ir_actions_report_xml
|
||||
|
|
|
@ -910,12 +910,11 @@ class mrp_production(osv.osv):
|
|||
# Internal shipment is created for Stockable and Consumer Products
|
||||
if production_line.product_id.type not in ('product', 'consu'):
|
||||
return False
|
||||
move_name = _('PROD: %s') % production.name
|
||||
source_location_id = production.location_src_id.id
|
||||
if not destination_location_id:
|
||||
destination_location_id = source_location_id
|
||||
return stock_move.create(cr, uid, {
|
||||
'name': move_name,
|
||||
'name': production.name,
|
||||
'picking_id': shipment_id,
|
||||
'product_id': production_line.product_id.id,
|
||||
'product_qty': production_line.product_qty,
|
||||
|
@ -965,9 +964,8 @@ class mrp_production(osv.osv):
|
|||
stock_move = self.pool.get('stock.move')
|
||||
source_location_id = production.product_id.product_tmpl_id.property_stock_production.id
|
||||
destination_location_id = production.location_dest_id.id
|
||||
move_name = _('PROD: %s') + production.name
|
||||
data = {
|
||||
'name': move_name,
|
||||
'name': production.name,
|
||||
'date': production.date_planned,
|
||||
'product_id': production.product_id.id,
|
||||
'product_qty': production.product_qty,
|
||||
|
@ -990,12 +988,11 @@ class mrp_production(osv.osv):
|
|||
# Internal shipment is created for Stockable and Consumer Products
|
||||
if production_line.product_id.type not in ('product', 'consu'):
|
||||
return False
|
||||
move_name = _('PROD: %s') % production.name
|
||||
destination_location_id = production.product_id.product_tmpl_id.property_stock_production.id
|
||||
if not source_location_id:
|
||||
source_location_id = production.location_src_id.id
|
||||
move_id = stock_move.create(cr, uid, {
|
||||
'name': move_name,
|
||||
'name': production.name,
|
||||
'date': production.date_planned,
|
||||
'product_id': production_line.product_id.id,
|
||||
'product_qty': production_line.product_qty,
|
||||
|
|
|
@ -78,6 +78,7 @@
|
|||
<!-- title -->
|
||||
<field name="name"/>
|
||||
</div>
|
||||
<div class="oe_clear"></div>
|
||||
<field name="tag_ids"/>
|
||||
<div class="oe_right">
|
||||
<t t-foreach="record.message_follower_ids.raw_value" t-as="follower">
|
||||
|
|
|
@ -348,6 +348,29 @@ class pos_session(osv.osv):
|
|||
statement.unlink(context=context)
|
||||
return True
|
||||
|
||||
|
||||
def open_cb(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
call the Point Of Sale interface and set the pos.session to 'opened' (in progress)
|
||||
"""
|
||||
if context is None:
|
||||
context = dict()
|
||||
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
|
||||
this_record = self.browse(cr, uid, ids[0], context=context)
|
||||
this_record._workflow_signal('open')
|
||||
|
||||
context.update(active_id=this_record.id)
|
||||
|
||||
return {
|
||||
'type' : 'ir.actions.client',
|
||||
'name' : _('Start Point Of Sale'),
|
||||
'tag' : 'pos.ui',
|
||||
'context' : context,
|
||||
}
|
||||
|
||||
def wkf_action_open(self, cr, uid, ids, context=None):
|
||||
# second browse because we need to refetch the data from the DB for cash_register_id
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
|
@ -439,10 +462,10 @@ class pos_session(osv.osv):
|
|||
context = {}
|
||||
if not ids:
|
||||
return {}
|
||||
context.update({'session_id' : ids[0]})
|
||||
context.update({'active_id': ids[0]})
|
||||
return {
|
||||
'type' : 'ir.actions.client',
|
||||
'name' : 'Start Point Of Sale',
|
||||
'name' : _('Start Point Of Sale'),
|
||||
'tag' : 'pos.ui',
|
||||
'context' : context,
|
||||
}
|
||||
|
|
|
@ -862,7 +862,7 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Point of Sale Session" version="7.0">
|
||||
<header>
|
||||
<button name="open" type="workflow" string="Validate & Open Session" states="opening_control" class="oe_highlight"/>
|
||||
<button name="open_cb" type="object" string="Validate & Open Session" states="opening_control" class="oe_highlight"/>
|
||||
<button name="open_frontend_cb" type="object" string="Continue Selling" states="opened"
|
||||
class="oe_highlight"/>
|
||||
<button name="cashbox_control" type="workflow" string="End of Session"
|
||||
|
@ -890,15 +890,14 @@
|
|||
<field name="cash_control" invisible="1" />
|
||||
<group>
|
||||
<field name="user_id"/>
|
||||
<field name="config_id" attrs="{'invisible' : [('config_id', '<>', False)]}"/>
|
||||
<field name="config_id" attrs="{'invisible' : [('config_id', '!=', False)]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="start_at" attrs="{'invisible' : [('state', '=', 'opening_control')]}"/>
|
||||
<field name="stop_at" attrs="{'invisible' : [('state', '!=', 'closed')]}"/>
|
||||
</group>
|
||||
<newline/>
|
||||
<group string="Opening Cash Control" attrs="{'invisible' : [ '&' ,'!', '&', ('state', '=', 'opening_control'), ('cash_control', '=', True), ('state', '!=',
|
||||
'closed')] }" >
|
||||
<group string="Opening Cash Control" attrs="{'invisible' : [('cash_control', '=', False)]}">
|
||||
<field name="opening_details_ids" nolabel="1" colspan="2" attrs="{'readonly' : [('state', 'not in', ('opening_control',))]}">
|
||||
<tree string="Opening Cashbox Lines" editable="bottom">
|
||||
<field name="pieces" readonly="1" />
|
||||
|
@ -907,8 +906,7 @@
|
|||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<group string="Closing Cash Control" attrs="{'invisible' : [ '&' ,'!', '&', ('state', '=', 'closing_control'), ('cash_control', '=', True), ('state', '!=',
|
||||
'closed')] }" >
|
||||
<group string="Closing Cash Control" attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', '=', 'opening_control')]}">
|
||||
<field name="details_ids" nolabel="1" colspan="2">
|
||||
<tree string="Cashbox Lines" editable="bottom">
|
||||
<field name="pieces" readonly="1" />
|
||||
|
@ -918,7 +916,7 @@
|
|||
</field>
|
||||
</group>
|
||||
|
||||
<div attrs="{'invisible' : [('state', '!=', 'closed')]}">
|
||||
<div attrs="{'invisible' : [('cash_control', '=', False)]}">
|
||||
<group class="oe_subtotal_footer oe_right">
|
||||
<field name="cash_register_balance_start" readonly="1" string="Opening Balance" class="oe_subtotal_footer_separator"/>
|
||||
<field name="cash_register_total_entry_encoding" attrs="{'invisible' : [('state', '=', 'opening_control')]}" string="+ Transactions"/>
|
||||
|
@ -935,7 +933,7 @@
|
|||
</div>
|
||||
|
||||
|
||||
<group class="oe_subtotal_footer oe_right" attrs="{'invisible': [('state', '!=', 'closed')]}">
|
||||
<group class="oe_subtotal_footer oe_right" attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', '=', 'opening_control')]}">
|
||||
<field name="cash_register_balance_end_real" class="oe_subtotal_footer_separator"/>
|
||||
<field name="cash_register_difference" class="oe_subtotal_footer_separator"/>
|
||||
</group>
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
'depends': [
|
||||
'base',
|
||||
'share',
|
||||
'auth_anonymous',
|
||||
'auth_signup',
|
||||
],
|
||||
'author': 'OpenERP SA',
|
||||
|
|
|
@ -19,34 +19,22 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import tools
|
||||
from osv import osv
|
||||
import tools
|
||||
|
||||
|
||||
class mail_mail_portal(osv.Model):
|
||||
""" Update of mail_mail class, to add the signin URL to notifications.
|
||||
"""
|
||||
_name = 'mail.mail'
|
||||
_inherit = ['mail.mail']
|
||||
|
||||
def _generate_signin_url(self, cr, uid, partner_id, portal_group_id, key, context=None):
|
||||
""" Generate the signin url """
|
||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='', context=context)
|
||||
return base_url + '/login?action=signin&partner_id=%s&group=%s&key=%s' % (partner_id, portal_group_id, key)
|
||||
class mail_mail(osv.Model):
|
||||
""" Update of mail_mail class, to add the signin URL to notifications. """
|
||||
_inherit = 'mail.mail'
|
||||
|
||||
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
|
||||
""" Return a specific ir_email body. The main purpose of this method
|
||||
is to be inherited by Portal, to add a link for signing in, in
|
||||
each notification email a partner receives.
|
||||
|
||||
""" add a signin link inside the body of a mail.mail
|
||||
:param mail: mail.mail browse_record
|
||||
:param partner: browse_record of the specific recipient partner
|
||||
:return: the resulting body_html
|
||||
"""
|
||||
body = super(mail_mail, self).send_get_mail_body(cr, uid, mail, partner, context=context)
|
||||
if partner:
|
||||
portal_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal')
|
||||
portal_id = portal_ref and portal_ref[1] or False
|
||||
url = self._generate_signin_url(cr, uid, partner.id, portal_id, 1234, context=context)
|
||||
body = tools.append_content_to_html(mail.body_html, url)
|
||||
return body
|
||||
else:
|
||||
return super(mail_mail_portal, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
||||
context = dict(context or {}, signup_valid=True)
|
||||
partner = self.pool.get('res.partner').browse(cr, uid, partner.id, context)
|
||||
body = tools.append_content_to_html(body, "Log in our portal at: %s" % partner.signup_url)
|
||||
return body
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!-- Top menu item -->
|
||||
<menuitem name="Portal"
|
||||
id="portal_menu"
|
||||
groups="base.group_no_one,portal.group_portal,auth_anonymous.group_anonymous"
|
||||
groups="base.group_no_one,portal.group_portal"
|
||||
sequence="20"/>
|
||||
|
||||
<menuitem name="Our company" id="portal_company" parent="portal_menu" sequence="10"/>
|
||||
|
|
|
@ -33,6 +33,7 @@ class test_portal(test_mail.TestMailMockups):
|
|||
self.mail_group = self.registry('mail.group')
|
||||
self.mail_mail = self.registry('mail.mail')
|
||||
self.mail_message = self.registry('mail.message')
|
||||
self.mail_invite = self.registry('mail.wizard.invite')
|
||||
self.res_users = self.registry('res.users')
|
||||
self.res_partner = self.registry('res.partner')
|
||||
|
||||
|
@ -41,19 +42,17 @@ class test_portal(test_mail.TestMailMockups):
|
|||
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
|
||||
|
||||
# Find Portal group
|
||||
group_portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal')
|
||||
self.group_portal_id = group_portal_ref and group_portal_ref[1] or False
|
||||
group_portal = self.registry('ir.model.data').get_object(cr, uid, 'portal', 'group_portal')
|
||||
self.group_portal_id = group_portal.id
|
||||
|
||||
# Create Chell (portal user)
|
||||
self.user_chell_id = self.res_users.create(cr, uid, {'name': 'Chell Gladys', 'login': 'chell', 'groups_id': [(6, 0, [self.group_portal_id])]})
|
||||
self.user_chell = self.res_users.browse(cr, uid, self.user_chell_id)
|
||||
self.partner_chell_id = self.user_chell.partner_id.id
|
||||
user_chell = self.res_users.browse(cr, uid, self.user_chell_id)
|
||||
self.partner_chell_id = user_chell.partner_id.id
|
||||
|
||||
def test_00_access_rights(self):
|
||||
""" Test basic mail_message and mail_group access rights for portal users. """
|
||||
cr, uid = self.cr, self.uid
|
||||
partner_chell_id = self.partner_chell_id
|
||||
user_chell_id = self.user_chell_id
|
||||
|
||||
# Prepare group: Pigs (portal)
|
||||
self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message')
|
||||
|
@ -64,56 +63,56 @@ class test_portal(test_mail.TestMailMockups):
|
|||
# ----------------------------------------
|
||||
|
||||
# Do: Chell reads Pigs messages, ok because restricted to portal group
|
||||
message_ids = self.mail_group.read(cr, user_chell_id, self.group_pigs_id, ['message_ids'])['message_ids']
|
||||
self.mail_message.read(cr, user_chell_id, message_ids)
|
||||
message_ids = self.mail_group.read(cr, self.user_chell_id, self.group_pigs_id, ['message_ids'])['message_ids']
|
||||
self.mail_message.read(cr, self.user_chell_id, message_ids)
|
||||
|
||||
# Do: Chell posts a message on Pigs, crash because can not write on group or is not in the followers
|
||||
self.assertRaises(except_orm,
|
||||
self.mail_group.message_post,
|
||||
cr, user_chell_id, self.group_pigs_id, body='Message')
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='Message')
|
||||
|
||||
# Do: Chell is added to Pigs followers
|
||||
self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [partner_chell_id])
|
||||
self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [self.partner_chell_id])
|
||||
|
||||
# Test: Chell posts a message on Pigs, ok because in the followers
|
||||
self.mail_group.message_post(cr, user_chell_id, self.group_pigs_id, body='Message')
|
||||
self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='Message')
|
||||
|
||||
def test_50_mail_invite(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
user_admin = self.res_users.browse(cr, uid, uid)
|
||||
self.mail_invite = self.registry('mail.wizard.invite')
|
||||
base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='')
|
||||
portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal')
|
||||
portal_id = portal_ref and portal_ref[1] or False
|
||||
|
||||
# 0 - Admin
|
||||
p_a_id = user_admin.partner_id.id
|
||||
partner_admin_id = user_admin.partner_id.id
|
||||
# 1 - Bert Tartopoils, with email, should receive emails for comments and emails
|
||||
p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
|
||||
partner_bert_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE1: generated URL
|
||||
# CASE: invite Bert to follow Pigs
|
||||
# ----------------------------------------
|
||||
|
||||
url = self.mail_mail._generate_signin_url(cr, uid, p_b_id, portal_id, 1234)
|
||||
self.assertEqual(url, base_url + '/login?action=signin&partner_id=%s&group=%s&key=%s' % (p_b_id, portal_id, 1234),
|
||||
'generated signin URL incorrect')
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE2: invite Bert
|
||||
# ----------------------------------------
|
||||
|
||||
_sent_email_subject = 'Invitation to follow Pigs'
|
||||
_sent_email_body = append_content_to_html('<div>You have been invited to follow Pigs.</div>', url)
|
||||
|
||||
# Do: create a mail_wizard_invite, validate it
|
||||
self._init_mock_build_email()
|
||||
mail_invite_id = self.mail_invite.create(cr, uid, {'partner_ids': [(4, p_b_id)]}, {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id})
|
||||
context = {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id}
|
||||
mail_invite_id = self.mail_invite.create(cr, uid, {'partner_ids': [(4, partner_bert_id)]}, context)
|
||||
self.mail_invite.add_followers(cr, uid, [mail_invite_id])
|
||||
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
|
||||
# Test: Pigs followers should contain Admin and Bert
|
||||
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
|
||||
self.assertEqual(set(follower_ids), set([p_a_id, p_b_id]), 'Pigs followers after invite is incorrect')
|
||||
# Test: sent email subject, body
|
||||
self.assertEqual(set(follower_ids), set([partner_admin_id, partner_bert_id]), 'Pigs followers after invite is incorrect')
|
||||
|
||||
# Test: partner must have been prepared for signup
|
||||
partner_bert = self.res_partner.browse(cr, uid, partner_bert_id)
|
||||
self.assertTrue(partner_bert.signup_valid, 'partner has not been prepared for signup')
|
||||
self.assertTrue(base_url in partner_bert.signup_url, 'signup url is incorrect')
|
||||
self.assertTrue(cr.dbname in partner_bert.signup_url, 'signup url is incorrect')
|
||||
self.assertTrue(partner_bert.signup_token in partner_bert.signup_url, 'signup url is incorrect')
|
||||
|
||||
# Test: (pretend to) send email and check subject, body
|
||||
self.assertEqual(len(self._build_email_kwargs_list), 1, 'sent email number incorrect, should be only for Bert')
|
||||
for sent_email in self._build_email_kwargs_list:
|
||||
self.assertEqual(sent_email.get('subject'), _sent_email_subject, 'sent email subject incorrect')
|
||||
self.assertEqual(sent_email.get('body'), _sent_email_body, 'sent email body incorrect')
|
||||
self.assertEqual(sent_email.get('subject'), 'Invitation to follow Pigs',
|
||||
'subject of invitation email is incorrect')
|
||||
self.assertTrue('You have been invited to follow Pigs' in sent_email.get('body'),
|
||||
'body of invitation email is incorrect')
|
||||
self.assertTrue(partner_bert.signup_url in sent_email.get('body'),
|
||||
'body of invitation email does not contain signup url')
|
||||
|
|
|
@ -30,17 +30,19 @@ from openerp import SUPERUSER_ID
|
|||
from base.res.res_partner import _lang_get
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# welcome/goodbye email sent to portal users
|
||||
# welcome email sent to portal users
|
||||
# (note that calling '_' has no effect except exporting those strings for translation)
|
||||
WELCOME_EMAIL_SUBJECT = _("Your OpenERP account at %(company)s")
|
||||
WELCOME_EMAIL_BODY = _("""Dear %(name)s,
|
||||
|
||||
You have been given access to %(portal)s at %(url)s.
|
||||
You have been given access to %(portal)s.
|
||||
|
||||
Your login account data is:
|
||||
Database: %(db)s
|
||||
User: %(login)s
|
||||
Password: %(password)s
|
||||
Username: %(login)s
|
||||
|
||||
In order to complete the signin process, click on the following url:
|
||||
%(url)s
|
||||
|
||||
%(welcome_message)s
|
||||
|
||||
|
@ -49,28 +51,10 @@ OpenERP - Open Source Business Applications
|
|||
http://www.openerp.com
|
||||
""")
|
||||
|
||||
GOODBYE_EMAIL_SUBJECT = _("Your OpenERP account at %(company)s")
|
||||
GOODBYE_EMAIL_BODY = _("""Dear %(name)s,
|
||||
|
||||
Your access to %(portal)s has been withdrawn.
|
||||
|
||||
%(goodbye_message)s
|
||||
|
||||
--
|
||||
OpenERP - Open Source Business Applications
|
||||
http://www.openerp.com
|
||||
""")
|
||||
|
||||
# character sets for passwords, excluding 0, O, o, 1, I, l
|
||||
_PASSU = 'ABCDEFGHIJKLMNPQRSTUVWXYZ'
|
||||
_PASSL = 'abcdefghijkmnpqrstuvwxyz'
|
||||
_PASSD = '23456789'
|
||||
|
||||
def random_password():
|
||||
# get 3 uppercase letters, 3 lowercase letters, 2 digits, and shuffle them
|
||||
chars = map(random.choice, [_PASSU] * 3 + [_PASSL] * 3 + [_PASSD] * 2)
|
||||
random.shuffle(chars)
|
||||
return ''.join(chars)
|
||||
# temporary random stuff; user password is reset by signup process
|
||||
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
return ''.join(random.choice(chars) for i in xrange(12))
|
||||
|
||||
def extract_email(email):
|
||||
""" extract the email address from a user-friendly email address """
|
||||
|
@ -92,8 +76,6 @@ class wizard(osv.osv_memory):
|
|||
'user_ids': fields.one2many('portal.wizard.user', 'wizard_id', string='Users'),
|
||||
'welcome_message': fields.text(string='Invitation Message',
|
||||
help="This text is included in the email sent to new users of the portal."),
|
||||
'goodbye_message': fields.text(string='Withdrawal Message',
|
||||
help="This text is included in the email sent to users withdrawn from the portal."),
|
||||
}
|
||||
|
||||
def _default_portal(self, cr, uid, context):
|
||||
|
@ -164,6 +146,8 @@ class wizard_user(osv.osv_memory):
|
|||
user = self._create_user(cr, SUPERUSER_ID, wizard_user, context)
|
||||
if (not user.active) or (portal not in user.groups_id):
|
||||
user.write({'active': True, 'groups_id': [(4, portal.id)]})
|
||||
# prepare for the signup process
|
||||
user.partner_id.signup_prepare()
|
||||
wizard_user = self.browse(cr, SUPERUSER_ID, wizard_user.id, context)
|
||||
self._send_email(cr, uid, wizard_user, context)
|
||||
else:
|
||||
|
@ -174,8 +158,6 @@ class wizard_user(osv.osv_memory):
|
|||
user.write({'groups_id': [(3, portal.id)], 'active': False})
|
||||
else:
|
||||
user.write({'groups_id': [(3, portal.id)]})
|
||||
wizard_user = self.browse(cr, SUPERUSER_ID, wizard_user.id, context)
|
||||
self._send_email(cr, uid, wizard_user, context)
|
||||
|
||||
def _retrieve_user(self, cr, uid, wizard_user, context=None):
|
||||
""" retrieve the (possibly inactive) user corresponding to wizard_user.partner_id
|
||||
|
@ -208,7 +190,7 @@ class wizard_user(osv.osv_memory):
|
|||
return res_users.browse(cr, uid, user_id, context)
|
||||
|
||||
def _send_email(self, cr, uid, wizard_user, context=None):
|
||||
""" send notification email to a new/former portal user
|
||||
""" send notification email to a new portal user
|
||||
@param wizard_user: browse record of model portal.wizard.user
|
||||
@return: the id of the created mail.mail record
|
||||
"""
|
||||
|
@ -219,33 +201,23 @@ class wizard_user(osv.osv_memory):
|
|||
_('You must have an email address in your User Preferences to send emails.'))
|
||||
|
||||
# determine subject and body in the portal user's language
|
||||
url = self.pool.get('ir.config_parameter').get_param(cr, SUPERUSER_ID, 'web.base.url', context=this_context)
|
||||
user = self._retrieve_user(cr, SUPERUSER_ID, wizard_user, context)
|
||||
context = dict(this_context or {}, lang=user.lang)
|
||||
data = {
|
||||
'company': this_user.company_id.name,
|
||||
'portal': wizard_user.wizard_id.portal_id.name,
|
||||
'welcome_message': wizard_user.wizard_id.welcome_message or "",
|
||||
'goodbye_message': wizard_user.wizard_id.goodbye_message or "",
|
||||
'url': url or _("(missing url)"),
|
||||
'db': cr.dbname,
|
||||
'name': user.name,
|
||||
'login': user.login,
|
||||
'password': user.password,
|
||||
'name': user.name
|
||||
'url': user.signup_url,
|
||||
}
|
||||
if wizard_user.in_portal:
|
||||
subject = _(WELCOME_EMAIL_SUBJECT) % data
|
||||
body = _(WELCOME_EMAIL_BODY) % data
|
||||
else:
|
||||
subject = _(GOODBYE_EMAIL_SUBJECT) % data
|
||||
body = _(GOODBYE_EMAIL_BODY) % data
|
||||
|
||||
mail_mail = self.pool.get('mail.mail')
|
||||
mail_values = {
|
||||
'email_from': this_user.email,
|
||||
'email_to': user.email,
|
||||
'subject': subject,
|
||||
'body_html': '<pre>%s</pre>' % body,
|
||||
'subject': _(WELCOME_EMAIL_SUBJECT) % data,
|
||||
'body_html': '<pre>%s</pre>' % (_(WELCOME_EMAIL_BODY) % data),
|
||||
'state': 'outgoing',
|
||||
}
|
||||
return mail_mail.create(cr, uid, mail_values, context=this_context)
|
||||
|
|
|
@ -27,8 +27,6 @@
|
|||
<field name="user_ids"/>
|
||||
<field name="welcome_message"
|
||||
placeholder="This text is included in the email sent to new portal users."/>
|
||||
<field name="goodbye_message"
|
||||
placeholder="This text is included in the email sent to users withdrawn from the portal."/>
|
||||
<footer>
|
||||
<button string="Apply" name="action_apply" type="object" class="oe_highlight"/>
|
||||
or
|
||||
|
|
|
@ -6,7 +6,6 @@ instance.web.ViewManager.include({
|
|||
var self = this;
|
||||
var _super = this._super();
|
||||
this.process_help = this.action ? this.action.help : '';
|
||||
self.process_help = $(this.process_help).text();
|
||||
if(this.action) {
|
||||
this.process_model = this.action.res_model;
|
||||
} else {
|
||||
|
@ -240,9 +239,7 @@ instance.web.ViewManager.include({
|
|||
title: _t('Process')
|
||||
});
|
||||
var form_controller = pop.view_form;
|
||||
pop.on_write_completed.add_last(function() {
|
||||
self.initialize_process_view();
|
||||
});
|
||||
pop.on('on_write_complete', self, self.initialize_process_view);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<record id="product_uom_dozen" model="product.uom">
|
||||
<field name="category_id" ref="product.product_uom_categ_unit"/>
|
||||
<field name="name">Dozen</field>
|
||||
<field name="factor" eval="0.083"/>
|
||||
<field name="factor_inv" eval="12"/>
|
||||
<field name="uom_type">bigger</field>
|
||||
</record>
|
||||
<record id="product_uom_kgm" model="product.uom">
|
||||
|
@ -70,7 +70,7 @@
|
|||
<!-- 'tonne' is the most common spelling in english-speaking countries,
|
||||
the alternative is 'metric ton' in the US, abbreviated as 'mt' -->
|
||||
<field name="name">t</field>
|
||||
<field name="factor" eval="0.001"/>
|
||||
<field name="factor_inv" eval="1000"/>
|
||||
<field name="uom_type">bigger</field>
|
||||
</record>
|
||||
<record id="product_uom_meter" model="product.uom">
|
||||
|
@ -81,7 +81,7 @@
|
|||
<record id="product_uom_km" model="product.uom">
|
||||
<field name="category_id" ref="uom_categ_length"/>
|
||||
<field name="name">km</field>
|
||||
<field name="factor" eval="0.001"/>
|
||||
<field name="factor_inv" eval="1000"/>
|
||||
<field name="uom_type">bigger</field>
|
||||
</record>
|
||||
<record id="product_uom_cm" model="product.uom">
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</record>
|
||||
<record id="project_tt_deployment" model="project.task.type">
|
||||
<field name="sequence">7</field>
|
||||
<field name="name">Deployment</field>
|
||||
<field name="name">Done</field>
|
||||
<field name="state">done</field>
|
||||
<field name="case_default" eval="True"/>
|
||||
</record>
|
||||
|
|
|
@ -279,7 +279,7 @@ class project_issue(base_stage, osv.osv):
|
|||
'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c),
|
||||
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
|
||||
'priority': crm.AVAILABLE_PRIORITIES[2][0],
|
||||
}
|
||||
}
|
||||
|
||||
_group_by_full = {
|
||||
'stage_id': _read_group_stage_ids
|
||||
|
|
|
@ -28,6 +28,7 @@ from tools.translate import _
|
|||
|
||||
class project_project(osv.osv):
|
||||
_inherit = 'project.project'
|
||||
|
||||
def onchange_partner_id(self, cr, uid, ids, part=False, context=None):
|
||||
res = super(project_project, self).onchange_partner_id(cr, uid, ids, part, context)
|
||||
if part and res and ('value' in res):
|
||||
|
@ -68,6 +69,7 @@ class project_project(osv.osv):
|
|||
'nodestroy': True,
|
||||
'help': help
|
||||
}
|
||||
|
||||
project_project()
|
||||
|
||||
class project_work(osv.osv):
|
||||
|
@ -81,7 +83,7 @@ class project_work(osv.osv):
|
|||
user_name = self.pool.get('res.users').read(cr, uid, [user_id], ['name'])[0]['name']
|
||||
raise osv.except_osv(_('Bad Configuration !'),
|
||||
_('Please define employee for user "%s". You must create one.')% (user_name,))
|
||||
emp = self.pool.get('hr.employee').browse(cr, uid, emp_id[0])
|
||||
emp = emp_obj.browse(cr, uid, emp_id[0])
|
||||
if not emp.product_id:
|
||||
raise osv.except_osv(_('Bad Configuration !'),
|
||||
_('Please define product on the related employee.\nFill in the timesheet tab of the employee form.'))
|
||||
|
@ -90,44 +92,44 @@ class project_work(osv.osv):
|
|||
raise osv.except_osv(_('Bad Configuration !'),
|
||||
_('Please define journal on the related employee.\nFill in the timesheet tab of the employee form.'))
|
||||
|
||||
a = emp.product_id.product_tmpl_id.property_account_expense.id
|
||||
if not a:
|
||||
a = emp.product_id.categ_id.property_account_expense_categ.id
|
||||
if not a:
|
||||
acc_id = emp.product_id.product_tmpl_id.property_account_expense.id
|
||||
if not acc_id:
|
||||
acc_id = emp.product_id.categ_id.property_account_expense_categ.id
|
||||
if not acc_id:
|
||||
raise osv.except_osv(_('Bad Configuration !'),
|
||||
_('Please define product and product category property account on the related employee.\nFill in the timesheet tab of the employee form.'))
|
||||
|
||||
res['product_id'] = emp.product_id.id
|
||||
res['journal_id'] = emp.journal_id.id
|
||||
res['general_account_id'] = a
|
||||
res['general_account_id'] = acc_id
|
||||
res['product_uom_id'] = emp.product_id.uom_id.id
|
||||
return res
|
||||
|
||||
def create(self, cr, uid, vals, *args, **kwargs):
|
||||
obj_timesheet = self.pool.get('hr.analytic.timesheet')
|
||||
project_obj = self.pool.get('project.project')
|
||||
timesheet_obj = self.pool.get('hr.analytic.timesheet')
|
||||
task_obj = self.pool.get('project.task')
|
||||
uom_obj = self.pool.get('product.uom')
|
||||
|
||||
vals_line = {}
|
||||
context = kwargs.get('context', {})
|
||||
if not context.get('no_analytic_entry',False):
|
||||
obj_task = task_obj.browse(cr, uid, vals['task_id'])
|
||||
task_obj = task_obj.browse(cr, uid, vals['task_id'])
|
||||
result = self.get_user_related_details(cr, uid, vals.get('user_id', uid))
|
||||
vals_line['name'] = '%s: %s' % (tools.ustr(obj_task.name), tools.ustr(vals['name']) or '/')
|
||||
vals_line['name'] = '%s: %s' % (tools.ustr(task_obj.name), tools.ustr(vals['name']) or '/')
|
||||
vals_line['user_id'] = vals['user_id']
|
||||
vals_line['product_id'] = result['product_id']
|
||||
vals_line['date'] = vals['date'][:10]
|
||||
|
||||
#calculate quantity based on employee's product's uom
|
||||
# Calculate quantity based on employee's product's uom
|
||||
vals_line['unit_amount'] = vals['hours']
|
||||
|
||||
default_uom = self.pool.get('res.users').browse(cr, uid, uid).company_id.project_time_mode_id.id
|
||||
if result['product_uom_id'] != default_uom:
|
||||
vals_line['unit_amount'] = uom_obj._compute_qty(cr, uid, default_uom, vals['hours'], result['product_uom_id'])
|
||||
acc_id = obj_task.project_id and obj_task.project_id.analytic_account_id.id or False
|
||||
acc_id = task_obj.project_id and task_obj.project_id.analytic_account_id.id or False
|
||||
if acc_id:
|
||||
vals_line['account_id'] = acc_id
|
||||
res = obj_timesheet.on_change_account_id(cr, uid, False, acc_id)
|
||||
res = timesheet_obj.on_change_account_id(cr, uid, False, acc_id)
|
||||
if res.get('value'):
|
||||
vals_line.update(res['value'])
|
||||
vals_line['general_account_id'] = result['general_account_id']
|
||||
|
@ -137,27 +139,29 @@ class project_work(osv.osv):
|
|||
amount = vals_line['unit_amount']
|
||||
prod_id = vals_line['product_id']
|
||||
unit = False
|
||||
timeline_id = obj_timesheet.create(cr, uid, vals=vals_line, context=context)
|
||||
timeline_id = timesheet_obj.create(cr, uid, vals=vals_line, context=context)
|
||||
|
||||
# Compute based on pricetype
|
||||
amount_unit = obj_timesheet.on_change_unit_amount(cr, uid, timeline_id,
|
||||
amount_unit = timesheet_obj.on_change_unit_amount(cr, uid, timeline_id,
|
||||
prod_id, amount, False, unit, vals_line['journal_id'], context=context)
|
||||
if amount_unit and 'amount' in amount_unit.get('value',{}):
|
||||
updv = { 'amount': amount_unit['value']['amount'] }
|
||||
obj_timesheet.write(cr, uid, [timeline_id], updv, context=context)
|
||||
timesheet_obj.write(cr, uid, [timeline_id], updv, context=context)
|
||||
vals['hr_analytic_timesheet_id'] = timeline_id
|
||||
return super(project_work,self).create(cr, uid, vals, *args, **kwargs)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
"""
|
||||
When a project task work gets updated, handle its hr analytic timesheet.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
timesheet_obj = self.pool.get('hr.analytic.timesheet')
|
||||
project_obj = self.pool.get('project.project')
|
||||
uom_obj = self.pool.get('product.uom')
|
||||
result = {}
|
||||
|
||||
if isinstance(ids, (long, int)):
|
||||
ids = [ids,]
|
||||
ids = [ids]
|
||||
|
||||
for task in self.browse(cr, uid, ids, context=context):
|
||||
line_id = task.hr_analytic_timesheet_id
|
||||
|
@ -165,25 +169,28 @@ class project_work(osv.osv):
|
|||
# if a record is deleted from timesheet, the line_id will become
|
||||
# null because of the foreign key on-delete=set null
|
||||
continue
|
||||
|
||||
vals_line = {}
|
||||
if 'name' in vals:
|
||||
vals_line['name'] = '%s: %s' % (tools.ustr(task.task_id.name), tools.ustr(vals['name']) or '/')
|
||||
if 'user_id' in vals:
|
||||
vals_line['user_id'] = vals['user_id']
|
||||
result = self.get_user_related_details(cr, uid, vals.get('user_id', task.user_id.id))
|
||||
for fld in ('product_id', 'general_account_id', 'journal_id', 'product_uom_id'):
|
||||
if result.get(fld, False):
|
||||
vals_line[fld] = result[fld]
|
||||
|
||||
if 'date' in vals:
|
||||
vals_line['date'] = vals['date'][:10]
|
||||
if 'hours' in vals:
|
||||
default_uom = self.pool.get('res.users').browse(cr, uid, uid).company_id.project_time_mode_id.id
|
||||
vals_line['unit_amount'] = vals['hours']
|
||||
prod_id = vals_line.get('product_id', line_id.product_id.id) # False may be set
|
||||
|
||||
if result.get('product_uom_id',False) and (not result['product_uom_id'] == default_uom):
|
||||
vals_line['unit_amount'] = uom_obj._compute_qty(cr, uid, default_uom, vals['hours'], result['product_uom_id'])
|
||||
# Put user related details in analytic timesheet values
|
||||
details = self.get_user_related_details(cr, uid, vals.get('user_id', task.user_id.id))
|
||||
for field in ('product_id', 'general_account_id', 'journal_id', 'product_uom_id'):
|
||||
if details.get(field, False):
|
||||
vals_line[field] = details[field]
|
||||
|
||||
# Check if user's default UOM differs from product's UOM
|
||||
user_default_uom_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.project_time_mode_id.id
|
||||
if details.get('product_uom_id', False) and details['product_uom_id'] != user_default_uom_id:
|
||||
vals_line['unit_amount'] = uom_obj._compute_qty(cr, uid, user_default_uom_id, vals['hours'], details['product_uom_id'])
|
||||
|
||||
# Compute based on pricetype
|
||||
amount_unit = timesheet_obj.on_change_unit_amount(cr, uid, line_id.id,
|
||||
|
@ -203,7 +210,7 @@ class project_work(osv.osv):
|
|||
for task in self.browse(cr, uid, ids):
|
||||
if task.hr_analytic_timesheet_id:
|
||||
hat_ids.append(task.hr_analytic_timesheet_id.id)
|
||||
# delete entry from timesheet too while deleting entry to task.
|
||||
# Delete entry from timesheet too while deleting entry to task.
|
||||
if hat_ids:
|
||||
hat_obj.unlink(cr, uid, hat_ids, *args, **kwargs)
|
||||
return super(project_work,self).unlink(cr, uid, ids, *args, **kwargs)
|
||||
|
@ -231,12 +238,11 @@ class task(osv.osv):
|
|||
if vals.get('project_id',False) or vals.get('name',False):
|
||||
vals_line = {}
|
||||
hr_anlytic_timesheet = self.pool.get('hr.analytic.timesheet')
|
||||
task_obj_l = self.browse(cr, uid, ids, context=context)
|
||||
if vals.get('project_id',False):
|
||||
project_obj = self.pool.get('project.project').browse(cr, uid, vals['project_id'], context=context)
|
||||
acc_id = project_obj.analytic_account_id.id
|
||||
|
||||
for task_obj in task_obj_l:
|
||||
for task_obj in self.browse(cr, uid, ids, context=context):
|
||||
if len(task_obj.work_ids):
|
||||
for task_work in task_obj.work_ids:
|
||||
if not task_work.hr_analytic_timesheet_id:
|
||||
|
@ -253,16 +259,19 @@ task()
|
|||
|
||||
class res_partner(osv.osv):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
def unlink(self, cursor, user, ids, context=None):
|
||||
parnter_id=self.pool.get('project.project').search(cursor, user, [('partner_id', 'in', ids)])
|
||||
if parnter_id:
|
||||
raise osv.except_osv(_('Invalid Action!'), _('You cannot delete a partner which is assigned to project, but you can uncheck the active box.'))
|
||||
return super(res_partner,self).unlink(cursor, user, ids,
|
||||
context=context)
|
||||
|
||||
res_partner()
|
||||
|
||||
class account_analytic_line(osv.osv):
|
||||
_inherit = "account.analytic.line"
|
||||
|
||||
def on_change_account_id(self, cr, uid, ids, account_id):
|
||||
res = {}
|
||||
if not account_id:
|
||||
|
@ -274,5 +283,7 @@ class account_analytic_line(osv.osv):
|
|||
if acc.state == 'close' or acc.state == 'cancelled':
|
||||
raise osv.except_osv(_('Invalid Analytic Account !'), _('You cannot select a Analytic Account which is in Close or Cancelled state.'))
|
||||
return res
|
||||
|
||||
account_analytic_line()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
Create a timesheet sheet for HR manager
|
||||
-
|
||||
!record {model: hr_timesheet_sheet.sheet, id: hr_timesheet_sheet_sheet_sheetforhrmanager0}:
|
||||
date_current: !eval time.strftime('%Y-05-%d')
|
||||
date_from: !eval "'%s-05-01' %(datetime.now().year)"
|
||||
date_to: !eval "'%s-05-31' %(datetime.now().year)"
|
||||
name: Sheet for hr manager
|
||||
|
|
|
@ -7,14 +7,14 @@ msgstr ""
|
|||
"Project-Id-Version: OpenERP Server 5.0.4\n"
|
||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
||||
"POT-Creation-Date: 2012-02-08 01:37+0100\n"
|
||||
"PO-Revision-Date: 2012-02-08 02:55+0000\n"
|
||||
"PO-Revision-Date: 2012-10-08 16:00+0000\n"
|
||||
"Last-Translator: kifcaliph <Unknown>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-09-07 04:56+0000\n"
|
||||
"X-Generator: Launchpad (build 15914)\n"
|
||||
"X-Launchpad-Export-Date: 2012-10-09 04:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16112)\n"
|
||||
|
||||
#. module: purchase
|
||||
#: model:process.transition,note:purchase.process_transition_confirmingpurchaseorder0
|
||||
|
@ -45,7 +45,7 @@ msgstr "المقصد"
|
|||
#: code:addons/purchase/purchase.py:236
|
||||
#, python-format
|
||||
msgid "In order to delete a purchase order, it must be cancelled first!"
|
||||
msgstr ""
|
||||
msgstr "من أجل حذف أمر الشراء، يجب الإلغاء أولاً!"
|
||||
|
||||
#. module: purchase
|
||||
#: help:purchase.report,date:0
|
||||
|
@ -87,7 +87,7 @@ msgstr ""
|
|||
#. module: purchase
|
||||
#: view:purchase.order:0
|
||||
msgid "Approved purchase order"
|
||||
msgstr ""
|
||||
msgstr "الموافقة على أمر الشراء"
|
||||
|
||||
#. module: purchase
|
||||
#: view:purchase.order:0 field:purchase.order,partner_id:0
|
||||
|
@ -149,7 +149,7 @@ msgstr "لا يوجد قائمة اسعار !"
|
|||
#. module: purchase
|
||||
#: model:ir.model,name:purchase.model_purchase_config_wizard
|
||||
msgid "purchase.config.wizard"
|
||||
msgstr ""
|
||||
msgstr "شراء.ضبط.صندوق حوار"
|
||||
|
||||
#. module: purchase
|
||||
#: view:board.board:0 model:ir.actions.act_window,name:purchase.purchase_draft
|
||||
|
@ -249,7 +249,7 @@ msgstr "يوم"
|
|||
#. module: purchase
|
||||
#: selection:purchase.order,invoice_method:0
|
||||
msgid "Based on generated draft invoice"
|
||||
msgstr ""
|
||||
msgstr "على أساس توليد مسودة فاتورة"
|
||||
|
||||
#. module: purchase
|
||||
#: view:purchase.report:0
|
||||
|
@ -259,7 +259,7 @@ msgstr "ترتيب اليوم"
|
|||
#. module: purchase
|
||||
#: view:board.board:0
|
||||
msgid "Monthly Purchases by Category"
|
||||
msgstr ""
|
||||
msgstr "مشتريات شهرية حسب الفئة"
|
||||
|
||||
#. module: purchase
|
||||
#: model:ir.actions.act_window,name:purchase.action_purchase_line_product_tree
|
||||
|
@ -269,7 +269,7 @@ msgstr "المشتريات"
|
|||
#. module: purchase
|
||||
#: view:purchase.order:0
|
||||
msgid "Purchase order which are in draft state"
|
||||
msgstr ""
|
||||
msgstr "أمر الشراء التي هي في حالة مسودة"
|
||||
|
||||
#. module: purchase
|
||||
#: view:purchase.order:0
|
||||
|
@ -392,7 +392,7 @@ msgstr "حركة مخزن"
|
|||
#: code:addons/purchase/purchase.py:419
|
||||
#, python-format
|
||||
msgid "You must first cancel all invoices related to this purchase order."
|
||||
msgstr ""
|
||||
msgstr "يجب عليك أولا إلغاء جميع الفواتير المتعلقة بهذا أمر الشراء."
|
||||
|
||||
#. module: purchase
|
||||
#: field:purchase.report,dest_address_id:0
|
||||
|
@ -436,13 +436,13 @@ msgstr "تم التحقق من الصلاحية عن طريق"
|
|||
#. module: purchase
|
||||
#: view:purchase.report:0
|
||||
msgid "Order in last month"
|
||||
msgstr ""
|
||||
msgstr "طلب في الشهر الماضي"
|
||||
|
||||
#. module: purchase
|
||||
#: code:addons/purchase/purchase.py:412
|
||||
#, python-format
|
||||
msgid "You must first cancel all receptions related to this purchase order."
|
||||
msgstr ""
|
||||
msgstr "يجب عليك أولا إلغاء جميع حفلات الاستقبال المتعلقة بهذا أمر الشراء."
|
||||
|
||||
#. module: purchase
|
||||
#: selection:purchase.order.line,state:0
|
||||
|
@ -467,7 +467,7 @@ msgstr "توضح انه قد تم عمل الاختيار"
|
|||
#. module: purchase
|
||||
#: view:purchase.order:0
|
||||
msgid "Purchase orders which are in exception state"
|
||||
msgstr ""
|
||||
msgstr "أوامر الشراء التي هي في حالة الاستثناء"
|
||||
|
||||
#. module: purchase
|
||||
#: report:purchase.order:0 field:purchase.report,validator:0
|
||||
|
@ -506,7 +506,7 @@ msgstr "تأكيد"
|
|||
#: model:ir.ui.menu,name:purchase.menu_action_picking_tree4_picking_to_invoice
|
||||
#: selection:purchase.order,invoice_method:0
|
||||
msgid "Based on receptions"
|
||||
msgstr ""
|
||||
msgstr "استناداً على ما تم استقباله"
|
||||
|
||||
#. module: purchase
|
||||
#: constraint:res.company:0
|
||||
|
@ -541,7 +541,7 @@ msgstr ""
|
|||
#. module: purchase
|
||||
#: view:purchase.order:0
|
||||
msgid "Purchase order which are in the exception state"
|
||||
msgstr ""
|
||||
msgstr "أمر الشراء التي هي في حالة استثناء"
|
||||
|
||||
#. module: purchase
|
||||
#: model:ir.actions.act_window,help:purchase.action_stock_move_report_po
|
||||
|
@ -597,7 +597,7 @@ msgstr "السعر الإجمالي"
|
|||
#. module: purchase
|
||||
#: model:ir.actions.act_window,name:purchase.action_import_create_supplier_installer
|
||||
msgid "Create or Import Suppliers"
|
||||
msgstr ""
|
||||
msgstr "إنشاء أو استيراد الموردون"
|
||||
|
||||
#. module: purchase
|
||||
#: view:stock.picking:0
|
||||
|
@ -662,7 +662,7 @@ msgstr ""
|
|||
#. module: purchase
|
||||
#: report:purchase.order:0
|
||||
msgid "Purchase Order Confirmation N°"
|
||||
msgstr ""
|
||||
msgstr "تأكيد أمر الشراء ن°"
|
||||
|
||||
#. module: purchase
|
||||
#: model:ir.actions.act_window,help:purchase.action_purchase_order_report_all
|
||||
|
@ -748,7 +748,7 @@ msgstr "الاستقبالات"
|
|||
#: code:addons/purchase/purchase.py:285
|
||||
#, python-format
|
||||
msgid "You cannot confirm a purchase order without any lines."
|
||||
msgstr ""
|
||||
msgstr "لا يمكنك تأكيد أمر الشراء دون أية أسطر."
|
||||
|
||||
#. module: purchase
|
||||
#: model:ir.actions.act_window,help:purchase.action_invoice_pending
|
||||
|
@ -784,7 +784,7 @@ msgstr "يناير"
|
|||
#. module: purchase
|
||||
#: model:ir.actions.server,name:purchase.ir_actions_server_edi_purchase
|
||||
msgid "Auto-email confirmed purchase orders"
|
||||
msgstr ""
|
||||
msgstr "البريد التلقائي لتأكيد طلبات الشراء"
|
||||
|
||||
#. module: purchase
|
||||
#: model:process.transition,name:purchase.process_transition_approvingpurchaseorder0
|
||||
|
@ -840,7 +840,7 @@ msgstr "دمج امر الشراء"
|
|||
#. module: purchase
|
||||
#: view:purchase.report:0
|
||||
msgid "Order in current month"
|
||||
msgstr ""
|
||||
msgstr "طلب في الشهر الحالي"
|
||||
|
||||
#. module: purchase
|
||||
#: view:purchase.report:0 field:purchase.report,delay_pass:0
|
||||
|
@ -881,7 +881,7 @@ msgstr "خطوط الاوامر الكلية للمستخدم لكل شهر"
|
|||
#. module: purchase
|
||||
#: view:purchase.order:0
|
||||
msgid "Approved purchase orders"
|
||||
msgstr ""
|
||||
msgstr "تأكيد أوامر الشراء"
|
||||
|
||||
#. module: purchase
|
||||
#: view:purchase.report:0 field:purchase.report,month:0
|
||||
|
@ -891,7 +891,7 @@ msgstr "شهر"
|
|||
#. module: purchase
|
||||
#: model:email.template,subject:purchase.email_template_edi_purchase
|
||||
msgid "${object.company_id.name} Order (Ref ${object.name or 'n/a' })"
|
||||
msgstr ""
|
||||
msgstr "${object.company_id.name} أمر (Ref ${object.name or 'n/a' })"
|
||||
|
||||
#. module: purchase
|
||||
#: report:purchase.quotation:0
|
||||
|
@ -932,7 +932,7 @@ msgstr "ـكون هذه القائمة المختارة التي تم جمعها
|
|||
#. module: purchase
|
||||
#: view:stock.picking:0
|
||||
msgid "Is a Back Order"
|
||||
msgstr ""
|
||||
msgstr "طلب عودة"
|
||||
|
||||
#. module: purchase
|
||||
#: model:process.node,note:purchase.process_node_invoiceafterpacking0
|
||||
|
@ -984,7 +984,7 @@ msgstr ""
|
|||
#. module: purchase
|
||||
#: selection:purchase.config.wizard,default_method:0
|
||||
msgid "Pre-Generate Draft Invoices based on Purchase Orders"
|
||||
msgstr ""
|
||||
msgstr "قبل انشاء مسودة فاتورة على طلبات الشراء"
|
||||
|
||||
#. module: purchase
|
||||
#: model:ir.actions.act_window,name:purchase.action_view_purchase_line_invoice
|
||||
|
@ -1010,7 +1010,7 @@ msgstr "عرض النتيجة"
|
|||
#. module: purchase
|
||||
#: selection:purchase.config.wizard,default_method:0
|
||||
msgid "Based on Purchase Order Lines"
|
||||
msgstr ""
|
||||
msgstr "اعتماداً على سطور طلب الشراء"
|
||||
|
||||
#. module: purchase
|
||||
#: help:purchase.order,amount_untaxed:0
|
||||
|
@ -1130,7 +1130,7 @@ msgstr "مرشحات مفصلة..."
|
|||
#. module: purchase
|
||||
#: view:purchase.config.wizard:0
|
||||
msgid "Invoicing Control on Purchases"
|
||||
msgstr ""
|
||||
msgstr "التحكم في الفاتورة عند الشراء"
|
||||
|
||||
#. module: purchase
|
||||
#: code:addons/purchase/wizard/purchase_order_group.py:48
|
||||
|
|
|
@ -50,6 +50,16 @@ class sale_order(osv.osv):
|
|||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
_description = "Sales Order"
|
||||
|
||||
def onchange_shop_id(self, cr, uid, ids, shop_id, context=None):
|
||||
v = {}
|
||||
if shop_id:
|
||||
shop = self.pool.get('sale.shop').browse(cr, uid, shop_id, context=context)
|
||||
if shop.project_id.id:
|
||||
v['project_id'] = shop.project_id.id
|
||||
if shop.pricelist_id.id:
|
||||
v['pricelist_id'] = shop.pricelist_id.id
|
||||
return {'value': v}
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if not default:
|
||||
default = {}
|
||||
|
@ -783,6 +793,7 @@ class sale_order_line(osv.osv):
|
|||
_('There is no Fiscal Position defined or Income category account defined for default properties of Product categories.'))
|
||||
res = {
|
||||
'name': line.name,
|
||||
'sequence': line.sequence,
|
||||
'origin': line.order_id.name,
|
||||
'account_id': account_id,
|
||||
'price_unit': pu,
|
||||
|
@ -900,7 +911,6 @@ class sale_order_line(osv.osv):
|
|||
fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
|
||||
if update_tax: #The quantity only have changed
|
||||
result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
|
||||
result.update({'type': product_obj.procure_method})
|
||||
|
||||
if not flag:
|
||||
result['name'] = self.pool.get('product.product').name_get(cr, uid, [product_obj.id], context=context_partner)[0][1]
|
||||
|
|
|
@ -181,7 +181,7 @@
|
|||
</group>
|
||||
<group>
|
||||
<field name="date_order"/>
|
||||
<field name="shop_id" invisible="1"/>
|
||||
<field name="shop_id" groups="base.group_no_one" on_change="onchange_shop_id(shop_id, context)" widget="selection"/>
|
||||
<field name="client_order_ref"/>
|
||||
<field domain="[('type','=','sale')]" name="pricelist_id" groups="product.group_sale_pricelist" on_change="onchange_pricelist_id(pricelist_id,order_line)"/>
|
||||
</group>
|
||||
|
@ -200,7 +200,7 @@
|
|||
<field name="product_id"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'shop':parent.shop_id, 'uom':product_uom}"
|
||||
groups="base.group_user"
|
||||
on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, False, True, parent.date_order, False, parent.fiscal_position, False, context)"/>
|
||||
on_change="product_id_change(parent.pricelist_id, product_id, product_uom_qty, product_uom, product_uos_qty, product_uos, name, parent.partner_id, False, True, parent.date_order, False, parent.fiscal_position, False, context)"/>
|
||||
<label for="product_uom_qty"/>
|
||||
<div>
|
||||
<field
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
partner_id: base.res_partner_2
|
||||
note: Invoice after delivery
|
||||
payment_term: account.account_payment_term
|
||||
order_line:
|
||||
- product_id: product.product_product_7
|
||||
product_uom_qty: 8
|
||||
-
|
||||
!record {model: sale.order.line, id: line}:
|
||||
name : 'LCD Monitor'
|
||||
order_id: sale_order_test1
|
||||
product_id: product.product_product_7
|
||||
price_unit: 190.50
|
||||
product_uom_qty: 8
|
||||
I verify that the onchange was correctly triggered
|
||||
-
|
||||
!assert {model: sale.order, id: sale.sale_order_test1, string: The onchange function of product was not correctly triggered}:
|
||||
- order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
|
||||
- order_line[0].price_unit == 1350.0
|
||||
|
|
|
@ -39,4 +39,14 @@ class sale_order_line(osv.osv):
|
|||
|
||||
sale_order_line()
|
||||
|
||||
class sale_order(osv.osv):
|
||||
_inherit = "sale.order"
|
||||
|
||||
def onchange_shop_id(self, cr, uid, ids, shop_id, context=None):
|
||||
# Remove the project_id from the result of super() call, if any, as this field is not in the view anymore
|
||||
res = super(sale_order, self).onchange_shop_id(cr, uid, ids, shop_id, context=context)
|
||||
if res.get('value',{}).get('project_id'):
|
||||
del(res['value']['project_id'])
|
||||
return res
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -117,26 +117,13 @@
|
|||
proc_ids = self.search(cr, uid, [('origin','=',so.name) and ('state','=','running')])
|
||||
assert proc_ids, _('Procurement is not in the running state!')
|
||||
-
|
||||
I verify that a manufacturing order has been generated
|
||||
I verify that a manufacturing order has been generated, and that its name and reference are correct
|
||||
-
|
||||
!python {model: sale.order}: |
|
||||
mnf_obj = self.pool.get('mrp.production')
|
||||
so = self.browse(cr, uid, ref("sale_order_so0"))
|
||||
mnf_obj=self.pool.get('mrp.production')
|
||||
mnf_id=mnf_obj.search(cr, uid, [('origin','=',so.name)])
|
||||
mnf_id = mnf_obj.search(cr, uid, [('origin','=',so.name)])
|
||||
assert mnf_id, _('Manufacturing order has not been generated')
|
||||
-
|
||||
I verify that a 'Sale Name' field of Manufacturing order gets bind with the value
|
||||
-
|
||||
!python {model: sale.order}: |
|
||||
so = self.browse(cr, uid, ref("sale_order_so0"))
|
||||
mnf_obj=self.pool.get('mrp.production')
|
||||
mnf_id=mnf_obj.search(cr, uid, [('sale_name','=',so.name)])
|
||||
assert mnf_id, _('Sale Name is not bind with the value')
|
||||
-
|
||||
I verify that a 'Sale Reference' field of Manufacturing order gets bind with the value
|
||||
-
|
||||
!python {model: sale.order}: |
|
||||
so = self.browse(cr, uid, ref("sale_order_so0"))
|
||||
mnf_obj=self.pool.get('mrp.production')
|
||||
mnf_id=mnf_obj.search(cr, uid, [('sale_ref','=',so.client_order_ref)])
|
||||
assert mnf_id, _('Sale Reference is not bind with the value')
|
||||
mo = mnf_obj.browse(cr, uid, mnf_id)[0]
|
||||
assert mo.sale_name == so.name, 'Wrong Name for the Manufacturing Order. Expected %s, Got %s' % (so.name, mo.name)
|
||||
assert mo.sale_ref == so.client_order_ref, 'Wrong Sale Reference for the Manufacturing Order'
|
||||
|
|
|
@ -61,6 +61,7 @@ You can choose flexible invoicing methods:
|
|||
'test': ['test/cancel_order_sale_stock.yml',
|
||||
'test/picking_order_policy.yml',
|
||||
'test/prepaid_order_policy.yml',
|
||||
'test/sale_order_onchange.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
|
|
|
@ -165,16 +165,6 @@ class sale_order(osv.osv):
|
|||
|
||||
return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
|
||||
|
||||
def onchange_shop_id(self, cr, uid, ids, shop_id):
|
||||
v = {}
|
||||
if shop_id:
|
||||
shop = self.pool.get('sale.shop').browse(cr, uid, shop_id)
|
||||
v['project_id'] = shop.project_id.id
|
||||
# Que faire si le client a une pricelist a lui ?
|
||||
if shop.pricelist_id.id:
|
||||
v['pricelist_id'] = shop.pricelist_id.id
|
||||
return {'value': v}
|
||||
|
||||
def action_view_delivery(self, cr, uid, ids, context=None):
|
||||
'''
|
||||
This function returns an action that display existing delivery orders of given sale order ids. It can either be a in a list or in a form view, if there is only one delivery order to show.
|
||||
|
@ -604,14 +594,18 @@ class sale_order_line(osv.osv):
|
|||
lang=lang, update_tax=update_tax, date_order=date_order, packaging=packaging, fiscal_position=fiscal_position, flag=flag, context=context)
|
||||
|
||||
if not product:
|
||||
return {'value': {'th_weight': 0, 'product_packaging': False,
|
||||
'product_uos_qty': qty}, 'domain': {'product_uom': [],
|
||||
'product_uos': []}}
|
||||
res['value'].update({'product_packaging': False})
|
||||
return res
|
||||
|
||||
#update of result obtained in super function
|
||||
res_packing = self.product_packaging_change(cr, uid, ids, pricelist, product, qty, uom, partner_id, packaging, context=context)
|
||||
res['value'].update(res_packing.get('value', {}))
|
||||
warning_msgs = res_packing.get('warning') and res_packing['warning']['message'] or ''
|
||||
product_obj = product_obj.browse(cr, uid, product, context=context)
|
||||
res['value']['delay'] = (product_obj.sale_delay or 0.0)
|
||||
res['value']['type'] = product_obj.procure_method
|
||||
|
||||
#check if product is available, and if not: raise an error
|
||||
uom2 = False
|
||||
if uom:
|
||||
uom2 = product_uom_obj.browse(cr, uid, uom)
|
||||
|
@ -619,7 +613,6 @@ class sale_order_line(osv.osv):
|
|||
uom = False
|
||||
if not uom2:
|
||||
uom2 = product_obj.uom_id
|
||||
|
||||
compare_qty = float_compare(product_obj.virtual_available * uom2.factor, qty * product_obj.uom_id.factor, precision_rounding=product_obj.uom_id.rounding)
|
||||
if (product_obj.type=='product') and int(compare_qty) == -1 \
|
||||
and (product_obj.procure_method=='make_to_stock'):
|
||||
|
@ -628,7 +621,8 @@ class sale_order_line(osv.osv):
|
|||
max(0,product_obj.virtual_available), product_obj.uom_id.name,
|
||||
max(0,product_obj.qty_available), product_obj.uom_id.name)
|
||||
warning_msgs += _("Not enough stock ! : ") + warn_msg + "\n\n"
|
||||
# get unit price
|
||||
|
||||
#update of warning messages
|
||||
if warning_msgs:
|
||||
warning = {
|
||||
'title': _('Configuration Error!'),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
|
||||
<record id="view_sale_shop_form_inherit" model="ir.ui.view">
|
||||
<field name="name">sale.shop.inherit.form</field>
|
||||
<field name="model">sale.shop</field>
|
||||
|
@ -13,7 +13,7 @@
|
|||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="view_shop_tree_inherit" model="ir.ui.view">
|
||||
<field name="name">sale.shop.sale.stock</field>
|
||||
<field name="model">sale.shop</field>
|
||||
|
@ -41,7 +41,8 @@
|
|||
</xpath>
|
||||
<xpath expr="//button[@name='action_view_invoice']" position="after">
|
||||
<button name="action_view_delivery" string="View Delivery Order" type="object" class="oe_highlight"
|
||||
attrs="{'invisible': ['|','|','|',('picking_ids','=',False),('picking_ids','=',[]), ('state', 'not in', ('progress','manual')),('shipped','=',True)]}"/> </xpath>
|
||||
attrs="{'invisible': ['|','|','|',('picking_ids','=',False),('picking_ids','=',[]), ('state', 'not in', ('progress','manual')),('shipped','=',True)]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='action_cancel']" position="after">
|
||||
<button name="ship_cancel" states="shipping_except" string="Cancel"/>
|
||||
</xpath>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue