[MERGE] forward port of branch 8.0 up to 8ac8281
@@ -685,8 +685,7 @@ Paulius Sladkevičius <komsas@gmail.com> Paulius Sladkevičius <paulius@hacbee.c
|
||||
Paulius Sladkevičius <komsas@gmail.com> Paulius Sladkevičius <paulius@hbee.eu>
|
||||
Paulius Sladkevičius <komsas@gmail.com> Paulius Sladkevičius <paulius@inovera.lt>
|
||||
Paulius Sladkevičius <komsas@gmail.com> paulius <>
|
||||
Pedro Manuel Baeza <pedro.baeza@serviciosbaeza.com>
|
||||
Pedro Manuel Baeza <pedro.baeza@serviciosbaeza.com> <pedro.baeza@gmail.com>
|
||||
Pedro Manuel Baeza <pedro.baeza@gmail.com> <pedro.baeza@serviciosbaeza.com>
|
||||
Peter Langenberg <pl@agaplan.eu>
|
||||
Peter Langenberg <pl@agaplan.eu> Peter Langenberg <>
|
||||
Philu <philubert@gmail.com> philubert@gmail.com <>
|
||||
|
||||
@@ -840,7 +840,7 @@ class account_invoice(models.Model):
|
||||
# create one move line for the total and possibly adjust the other lines amount
|
||||
total, total_currency, iml = inv.with_context(ctx).compute_invoice_totals(company_currency, ref, iml)
|
||||
|
||||
name = inv.name or inv.supplier_invoice_number or '/'
|
||||
name = inv.supplier_invoice_number or inv.name or '/'
|
||||
totlines = []
|
||||
if inv.payment_term:
|
||||
totlines = inv.with_context(ctx).payment_term.compute(total, date_invoice)[0]
|
||||
|
||||
@@ -251,13 +251,14 @@ class res_partner(osv.osv):
|
||||
def _journal_item_count(self, cr, uid, ids, field_name, arg, context=None):
|
||||
MoveLine = self.pool('account.move.line')
|
||||
AnalyticAccount = self.pool('account.analytic.account')
|
||||
return {
|
||||
partner_id: {
|
||||
'journal_item_count': MoveLine.search_count(cr, uid, [('partner_id', '=', partner_id)], context=context),
|
||||
'contracts_count': AnalyticAccount.search_count(cr, uid, [('partner_id', 'child_of', partner_id)], context=context)
|
||||
}
|
||||
for partner_id in ids
|
||||
}
|
||||
results = {}
|
||||
for partner_id in ids:
|
||||
results[partner_id] = {}
|
||||
if 'contracts_count' in field_name:
|
||||
results[partner_id]['contracts_count'] = AnalyticAccount.search_count(cr, uid, [('partner_id', '=', partner_id)], context=context)
|
||||
if 'journal_item_count' in field_name:
|
||||
results[partner_id]['journal_item_count'] = MoveLine.search_count(cr, uid, [('partner_id', 'child_of', partner_id)], context=context)
|
||||
return results
|
||||
|
||||
def has_something_to_reconcile(self, cr, uid, partner_id, context=None):
|
||||
'''
|
||||
|
||||
@@ -28,21 +28,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_template_search_view" model="ir.ui.view">
|
||||
<field name="name">product.template.search</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="inherit_id" ref="product.product_template_search_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="product_variant_ids" position="after">
|
||||
<field name="categ_id"/>
|
||||
</field>
|
||||
<xpath expr="//group[@string='Group by...']" position="inside">
|
||||
<filter string='Category' icon="terp-stock_symbol-selection" domain="[]" context="{'group_by' : 'categ_id'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_category_property_form" model="ir.ui.view">
|
||||
<field name="name">product.category.property.form.inherit</field>
|
||||
<field name="model">product.category</field>
|
||||
|
||||
@@ -1317,13 +1317,13 @@ openerp.account = function (instance) {
|
||||
if (balance_type === "equal") {
|
||||
displayValidState(true);
|
||||
} else if (balance_type === "greater") {
|
||||
createOpenBalance("Create Write-off");
|
||||
createOpenBalance(_t("Create Write-off"));
|
||||
} else if (balance_type === "lower") {
|
||||
if (self.st_line.has_no_partner) {
|
||||
createOpenBalance("Choose counterpart");
|
||||
createOpenBalance(_t("Choose counterpart"));
|
||||
} else {
|
||||
displayValidState(false, "Keep open");
|
||||
createOpenBalance("Open balance");
|
||||
createOpenBalance(_t("Open balance"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<span t-esc="formatLang(sum_balance_account(childrenaccount), digits=get_digits(dp='Account'), currency_obj=res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right" t-if="data['form']['amount_currency']">
|
||||
<span t-esc="formatLang(sum_currency_amount_account(childrenaccount), digits=get_digits(dp='Account'), currency_obj=res_company.currency_id) if sum_currency_amount_account(childrenaccount) > 0.00 else ''"/>
|
||||
<span t-esc="formatLang(sum_currency_amount_account(childrenaccount), digits=get_digits(dp='Account')) if sum_currency_amount_account(childrenaccount) > 0.00 else ''"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-foreach="lines(childrenaccount)" t-as="line">
|
||||
@@ -117,7 +117,7 @@
|
||||
<span t-esc="formatLang(line['progress'], digits=get_digits(dp='Account'), currency_obj=res_company.currency_id)"/>
|
||||
</td>
|
||||
<td class="text-right" t-if="data['form']['amount_currency']">
|
||||
<span t-esc="formatLang(line['amount_currency'], currency_obj=res_company.currency_id) if line['amount_currency'] > 0.00 else ''"/>
|
||||
<span t-esc="formatLang(line['amount_currency'], digits=get_digits(dp='Account')) if line['amount_currency'] > 0.00 else ''"/>
|
||||
<span t-esc="line['currency_code'] if line['amount_currency'] > 0.00 else ''"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -63,6 +63,8 @@ class account_fiscalyear_close(osv.osv_memory):
|
||||
raise UserError(_('The entries to reconcile should belong to the same company.'))
|
||||
r_id = self.pool.get('account.move.reconcile').create(cr, uid, {'type': 'auto', 'opening_reconciliation': True})
|
||||
cr.execute('update account_move_line set reconcile_id = %s where id in %s',(r_id, tuple(ids),))
|
||||
# reconcile_ref deptends from reconcile_id but was not recomputed
|
||||
obj_acc_move_line._store_set_values(cr, uid, ids, ['reconcile_ref'], context=context)
|
||||
obj_acc_move_line.invalidate_cache(cr, uid, ['reconcile_id'], ids, context=context)
|
||||
return r_id
|
||||
|
||||
|
||||
@@ -79,5 +79,7 @@ class account_statement_from_invoice_lines(osv.osv_memory):
|
||||
'statement_id': statement_id,
|
||||
'ref': line.ref,
|
||||
'date': statement.date,
|
||||
'amount_currency': line.amount_currency,
|
||||
'currency_id': line.currency_id.id,
|
||||
}, context=context)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import Entries">
|
||||
<separator string="Payable and Receivables"/>
|
||||
<field height="300" width="700" name="line_ids" domain="[('account_id.type','in',['receivable','payable']), ('reconcile_id','=',False), ('state', '=', 'valid')]"/>
|
||||
<field height="300" width="700"
|
||||
name="line_ids"
|
||||
domain="[('account_id.type','in',['receivable','payable']), ('reconcile_id','=',False), ('state', '=', 'valid')]"
|
||||
context="{'currency':True}" />
|
||||
<footer>
|
||||
<button string="Ok" name="populate_statement" type="object" class="oe_highlight"/>
|
||||
or
|
||||
|
||||
@@ -5,6 +5,8 @@ from passlib.context import CryptContext
|
||||
import openerp
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
openerp.addons.base.res.res_users.USER_PRIVATE_FIELDS.append('password_crypt')
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
default_crypt_context = CryptContext(
|
||||
|
||||
@@ -101,11 +101,11 @@
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname')}</div>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz)}</div>
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #E1E2F8;width: 130px;">
|
||||
${object.event_id.get_interval(object.event_id.start,'day')}
|
||||
${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz)}
|
||||
</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month')}</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz)}</div>
|
||||
<div style="border-collapse:separate;color:#8a89ba;text-align:center;width: 128px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval(object.event_id.start, 'time', tz=object.partner_id.tz) or ''}</div>
|
||||
</td>
|
||||
<td>
|
||||
@@ -226,11 +226,11 @@
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname')}</div>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz)}</div>
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #E1E2F8;width: 130px;">
|
||||
${object.event_id.get_interval(object.event_id.start,'day')}
|
||||
${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz)}
|
||||
</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month')}</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz)}</div>
|
||||
<div style="border-collapse:separate;color:#8a89ba;text-align:center;width: 128px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval(object.event_id.start, 'time', tz=object.partner_id.tz) or ''}</div>
|
||||
</td>
|
||||
<td>
|
||||
@@ -350,11 +350,11 @@
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname')}</div>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz)}</div>
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #E1E2F8;width: 130px;">
|
||||
${object.event_id.get_interval(object.event_id.start,'day')}
|
||||
${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz)}
|
||||
</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month')}</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz)}</div>
|
||||
<div style="border-collapse:separate;color:#8a89ba;text-align:center;width: 128px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval(object.event_id.start, 'time', tz=object.partner_id.tz) or ''}</div>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -35,8 +35,9 @@ class stock_picking(osv.osv):
|
||||
total_weight = total_weight_net = 0.00
|
||||
|
||||
for move in picking.move_lines:
|
||||
total_weight += move.weight
|
||||
total_weight_net += move.weight_net
|
||||
if move.state != 'cancel':
|
||||
total_weight += move.weight
|
||||
total_weight_net += move.weight_net
|
||||
|
||||
res[picking.id] = {
|
||||
'weight': total_weight,
|
||||
@@ -58,12 +59,12 @@ class stock_picking(osv.osv):
|
||||
'weight': fields.function(_cal_weight, type='float', string='Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight',
|
||||
store={
|
||||
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 40),
|
||||
'stock.move': (_get_picking_line, ['picking_id', 'product_id','product_uom_qty','product_uom'], 40),
|
||||
'stock.move': (_get_picking_line, ['state', 'picking_id', 'product_id','product_uom_qty','product_uom'], 40),
|
||||
}),
|
||||
'weight_net': fields.function(_cal_weight, type='float', string='Net Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight',
|
||||
store={
|
||||
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 40),
|
||||
'stock.move': (_get_picking_line, ['picking_id', 'product_id','product_uom_qty','product_uom'], 40),
|
||||
'stock.move': (_get_picking_line, ['state', 'picking_id', 'product_id','product_uom_qty','product_uom'], 40),
|
||||
}),
|
||||
'carrier_tracking_ref': fields.char('Carrier Tracking Ref', copy=False),
|
||||
'number_of_packages': fields.integer('Number of Packages', copy=False),
|
||||
|
||||
@@ -37,7 +37,10 @@ MAX_VISIBILITY_RANKING = 3
|
||||
def start_end_date_for_period(period, default_start_date=False, default_end_date=False):
|
||||
"""Return the start and end date for a goal period based on today
|
||||
|
||||
:return: (start_date, end_date), datetime.date objects, False if the period is
|
||||
:param str default_start_date: string date in DEFAULT_SERVER_DATE_FORMAT format
|
||||
:param str default_end_date: string date in DEFAULT_SERVER_DATE_FORMAT format
|
||||
|
||||
:return: (start_date, end_date), dates in string format, False if the period is
|
||||
not defined or unknown"""
|
||||
today = date.today()
|
||||
if period == 'daily':
|
||||
@@ -58,11 +61,9 @@ def start_end_date_for_period(period, default_start_date=False, default_end_date
|
||||
start_date = default_start_date # for manual goal, start each time
|
||||
end_date = default_end_date
|
||||
|
||||
if start_date and end_date:
|
||||
return (datetime.strftime(start_date, DF), datetime.strftime(end_date, DF))
|
||||
else:
|
||||
return (start_date, end_date)
|
||||
|
||||
return (datetime.strftime(start_date, DF), datetime.strftime(end_date, DF))
|
||||
|
||||
class gamification_challenge(osv.Model):
|
||||
"""Gamification challenge
|
||||
|
||||
@@ -16,37 +16,36 @@
|
||||
<div attrs="{'invisible':[('google_cal_sync','=',False)]}">
|
||||
<br/><h2>To setup the signin process with Google, first you have to perform the following steps</h2>
|
||||
<ul>
|
||||
<li> Connect on your google account and go to <a href='https://cloud.google.com/console' target='_blank'>https://cloud.google.com/console</a> </li>
|
||||
<li> Connect on your google account and go to <a href='https://console.developers.google.com/' target='_blank'>https://console.developers.google.com/</a> </li>
|
||||
<li>
|
||||
Click on <b>"Create Project"</b> and enter a project name and change your id if you want. Don't forget to accept the Terms of Services
|
||||
Click on <b>'Create Project'</b> and enter a project name and change your id if you want. Don't forget to accept the Terms of Services
|
||||
<br/><br/><img src='/google_calendar/static/src/img/setup_01.png' class='calendar_img_tuto'/>
|
||||
<br/><img src='/google_calendar/static/src/img/setup_02.png' class='calendar_img_tuto'/>
|
||||
</li>
|
||||
<li> In the menu on left side, select the sub menu APIs (from menu APIs and auth) and activate <b>"Calendar API"</b> by clicking on button "OFF".
|
||||
<br/> You will need to accept again the "Google APIs Terms of services" and "Calendar API Terms of service"
|
||||
<br/> When it's done, check that the button of <b>"Calendar API"</b> is well in green and with text "ON"
|
||||
<li> In the menu on left side, select the sub menu APIs (from menu APIs and auth) and click on <b>'Calendar API'</b>.
|
||||
<br/> Activate the Calendar API by clicking on the blue button <b>'Enable API'</b>.
|
||||
<br/> When it's done, the Calendar API overview will be available
|
||||
<br/><br/> <img src='/google_calendar/static/src/img/setup_03.png' class='calendar_img_tuto'/>
|
||||
<br/> <img src='/google_calendar/static/src/img/setup_04.png' class='calendar_img_tuto'/>
|
||||
<br/> <img src='/google_calendar/static/src/img/setup_05.png' class='calendar_img_tuto'/>
|
||||
</li>
|
||||
<li>
|
||||
In the menu on left side, select the sub menu <b>'Credentials'</b> (from menu APIs and auth) and click on button <b>'Create New Client ID'</b>
|
||||
In the menu on left side, select the sub menu <b>'Credentials'</b> (from menu APIs and auth) and click on button <b>'Create new Client ID'</b>
|
||||
<br/><br/> <img src='/google_calendar/static/src/img/setup_06.png' class='calendar_img_tuto'/>
|
||||
</li>
|
||||
<li> Fill in the Name of application and check that the platform is well on <b>'Web Application'</b>.<br/>
|
||||
<br/> You should now configure the allowed pages on which you will be redirected. To do it, you need to complete the field <b>"Redirect RI"</b>
|
||||
and set as value (your own domain followed by <i>"/google_account/authentication"</i>) :
|
||||
<li> Check that the Application type is set on <b>'Web Application'</b>, then click on <b>'Configure consent screen'</b>.
|
||||
<br/> Specify an email address and a product name, then save.
|
||||
<br/><br/> <img src='/google_calendar/static/src/img/setup_07.png' class='calendar_img_tuto'/>
|
||||
<br/><img src='/google_calendar/static/src/img/setup_09.png' class='calendar_img_tuto'/>
|
||||
<br/> You should now configure the allowed pages on which you will be redirected. To do it, you need to complete the field <b>"Authorized redirect URI"</b>
|
||||
and set as value (your own domain followed by <i>'/google_account/authentication'</i>):
|
||||
<br/>==> <b><field name="server_uri" readonly="1" style='display:inline'/></b>
|
||||
<br/> You can now click on <b>'Create Client ID'</b>
|
||||
<br/><br/> <img src='/google_calendar/static/src/img/setup_07.png' class='calendar_img_tuto'/>
|
||||
<br/><br/> <img src='/google_calendar/static/src/img/setup_10.png' class='calendar_img_tuto'/>
|
||||
</li>
|
||||
<li>Once done, you will have the both informations (<b>Client ID</b> and <b>Client Secret</b>) that you need to insert in the 2 fields below !
|
||||
<li>Once done, you will have the both informations (<b>Client ID</b> and <b>Client Secret</b>) that you need to insert in the 2 fields below!
|
||||
<br/><br/> <img src='/google_calendar/static/src/img/setup_08.png' class='calendar_img_tuto'/>
|
||||
</li>
|
||||
<li>
|
||||
To finish, you just need to create a "consent screen" by clicking on <b>"Consent Screen"</b> in the left menu.
|
||||
<br/><br/> <img src='/google_calendar/static/src/img/setup_09.png' class='calendar_img_tuto'/>
|
||||
</li>
|
||||
<a href="#" class="oe_link">Return at Top</a>
|
||||
|
||||
</ul>
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 50 KiB |
@@ -6,7 +6,7 @@
|
||||
<record id="view_partner_inv_comm_type_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.inv_comm_type.form.inherit</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="inherit_id" ref="account.view_partner_property_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="last_reconciliation_date" position="after">
|
||||
<field name="out_inv_comm_type"/>
|
||||
|
||||
@@ -601,7 +601,8 @@ class mail_message(osv.Model):
|
||||
|
||||
def _find_allowed_model_wise(self, cr, uid, doc_model, doc_dict, context=None):
|
||||
doc_ids = doc_dict.keys()
|
||||
allowed_doc_ids = self.pool[doc_model].search(cr, uid, [('id', 'in', doc_ids)], context=context)
|
||||
ctx = dict(context or {}, active_test=False)
|
||||
allowed_doc_ids = self.pool[doc_model].search(cr, uid, [('id', 'in', doc_ids)], context=ctx)
|
||||
return set([message_id for allowed_doc_id in allowed_doc_ids for message_id in doc_dict[allowed_doc_id]])
|
||||
|
||||
def _find_allowed_doc_ids(self, cr, uid, model_ids, context=None):
|
||||
|
||||
@@ -590,6 +590,7 @@ class mail_thread(osv.AbstractModel):
|
||||
ir_attachment_obj.unlink(cr, uid, attach_ids, context=context)
|
||||
return True
|
||||
|
||||
@api.cr_uid_ids_context
|
||||
def check_mail_message_access(self, cr, uid, mids, operation, model_obj=None, context=None):
|
||||
""" mail.message check permission rules for related document. This method is
|
||||
meant to be inherited in order to implement addons-specific behavior.
|
||||
|
||||
@@ -206,6 +206,7 @@ class mrp_bom(osv.osv):
|
||||
}
|
||||
_order = "sequence"
|
||||
|
||||
|
||||
def _bom_find(self, cr, uid, product_tmpl_id=None, product_id=None, properties=None, context=None):
|
||||
""" Finds BoM for particular product and product uom.
|
||||
@param product_tmpl_id: Selected product.
|
||||
|
||||
@@ -220,7 +220,7 @@ class mrp_production(osv.osv):
|
||||
workcenter_line.signal_workflow('button_done')
|
||||
return super(mrp_production,self).action_production_end(cr, uid, ids, context=context)
|
||||
|
||||
def action_in_production(self, cr, uid, ids):
|
||||
def action_in_production(self, cr, uid, ids, context=None):
|
||||
""" Changes state to In Production and writes starting date.
|
||||
@return: True
|
||||
"""
|
||||
@@ -228,7 +228,7 @@ class mrp_production(osv.osv):
|
||||
for prod in self.browse(cr, uid, ids):
|
||||
if prod.workcenter_lines:
|
||||
workcenter_pool.signal_workflow(cr, uid, [prod.workcenter_lines[0].id], 'button_start_working')
|
||||
return super(mrp_production,self).action_in_production(cr, uid, ids)
|
||||
return super(mrp_production,self).action_in_production(cr, uid, ids, context=context)
|
||||
|
||||
def action_cancel(self, cr, uid, ids, context=None):
|
||||
""" Cancels work order if production order is canceled.
|
||||
|
||||
@@ -154,7 +154,7 @@ class PaymentAcquirerOgone(osv.Model):
|
||||
'OWNERZIP': partner_values['zip'],
|
||||
'OWNERADDRESS': partner_values['address'],
|
||||
'OWNERTOWN': partner_values['city'],
|
||||
'OWNERCTY': partner_values['country'] and partner_values['country'].name or '',
|
||||
'OWNERCTY': partner_values['country'] and partner_values['country'].code or '',
|
||||
'OWNERTELNO': partner_values['phone'],
|
||||
'ACCEPTURL': '%s' % urlparse.urljoin(base_url, OgoneController._accept_url),
|
||||
'DECLINEURL': '%s' % urlparse.urljoin(base_url, OgoneController._decline_url),
|
||||
|
||||
@@ -998,6 +998,7 @@ class pos_order(osv.osv):
|
||||
'invoice_id': inv_id,
|
||||
'product_id': line.product_id.id,
|
||||
'quantity': line.qty,
|
||||
'account_analytic_id': self._prepare_analytic_account(cr, uid, line, context=context),
|
||||
}
|
||||
inv_name = product_obj.name_get(cr, uid, [line.product_id.id], context=context)[0][1]
|
||||
inv_line.update(inv_line_ref.product_id_change(cr, uid, [],
|
||||
|
||||
@@ -807,7 +807,7 @@ class product_template(osv.osv):
|
||||
# re-apply product.template order + name_get
|
||||
return super(product_template, self).name_search(
|
||||
cr, user, '', args=[('id', 'in', template_ids)],
|
||||
operator=operator, context=context, limit=limit)
|
||||
operator='ilike', context=context, limit=limit)
|
||||
|
||||
class product_product(osv.osv):
|
||||
_name = "product.product"
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Tasks Analysis" type="pivot">
|
||||
<field name="project_id" type="row"/>
|
||||
<field name="user_id" type="col"/>
|
||||
<field name="nbr" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
|
||||
@@ -47,7 +47,8 @@ class project_configuration(osv.osv_memory):
|
||||
'module_rating_project': fields.boolean('Allow customers to rate provided services',
|
||||
help="This allows customers to give rating on provided services"),
|
||||
'time_unit': fields.many2one('product.uom', 'Working time unit', required=True,
|
||||
help="""This will set the unit of measure used in projects and tasks."""),
|
||||
help='This will set the unit of measure used in projects and tasks.\n'
|
||||
'Changing the unit will only impact new entries.'),
|
||||
'module_project_issue_sheet': fields.boolean("Invoice working time on issues",
|
||||
help='Provides timesheet support for the issues/bugs management in project.\n'
|
||||
'-This installs the module project_issue_sheet.'),
|
||||
|
||||
@@ -834,7 +834,7 @@ class purchase_order(osv.osv):
|
||||
picking_vals = {
|
||||
'picking_type_id': order.picking_type_id.id,
|
||||
'partner_id': order.partner_id.id,
|
||||
'date': max([l.date_planned for l in order.order_line]),
|
||||
'date': order.date_order,
|
||||
'origin': order.name
|
||||
}
|
||||
picking_id = self.pool.get('stock.picking').create(cr, uid, picking_vals, context=context)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Set a product as using average price.
|
||||
-
|
||||
!record {model: product.product, id: product_variable_icecream}:
|
||||
default_code: AVG
|
||||
default_code: VARAVG
|
||||
name: Variable Ice Cream
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Set a product as using fifo price
|
||||
-
|
||||
!record {model: product.product, id: product_fiforet_icecream}:
|
||||
default_code: FIFO
|
||||
default_code: FIFORET
|
||||
name: FIFO Ice Cream
|
||||
type: product
|
||||
standard_price: 0.0
|
||||
|
||||
@@ -45,6 +45,7 @@ class sale_report(osv.osv):
|
||||
'nbr': fields.integer('# of Lines', readonly=True), # TDE FIXME master: rename into nbr_lines
|
||||
'state': fields.selection([
|
||||
('draft', 'Quotation'),
|
||||
('sent', 'Quotation Sent'),
|
||||
('waiting_date', 'Waiting Schedule'),
|
||||
('manual', 'Manual In Progress'),
|
||||
('progress', 'In Progress'),
|
||||
|
||||
@@ -780,7 +780,7 @@ class sale_order(osv.osv):
|
||||
order_line.append([4, line_id])
|
||||
else:
|
||||
order_line.append(line)
|
||||
return {'value': {'order_line': order_line}}
|
||||
return {'value': {'order_line': order_line, 'amount_untaxed': False, 'amount_tax': False, 'amount_total': False}}
|
||||
|
||||
def test_procurements_done(self, cr, uid, ids, context=None):
|
||||
for sale in self.browse(cr, uid, ids, context=context):
|
||||
|
||||
@@ -37,7 +37,7 @@ class sale_order_line(osv.osv):
|
||||
to_cur = self.pool.get('product.pricelist').browse(cr, uid, [pricelist])[0].currency_id.id
|
||||
if product:
|
||||
product = self.pool['product.product'].browse(cr, uid, product, context=context)
|
||||
purchase_price = product.standard_price
|
||||
purchase_price = product.price_get(ptype='standard_price', context=dict(context, currency_id=to_cur))[product.id]
|
||||
to_uom = res.get('product_uom', uom)
|
||||
if to_uom != product.uom_id.id:
|
||||
purchase_price = self.pool['product.uom']._compute_price(cr, uid, product.uom_id.id, purchase_price, to_uom)
|
||||
|
||||
@@ -30,6 +30,7 @@ class sale_report(osv.osv):
|
||||
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse',readonly=True),
|
||||
'state': fields.selection([
|
||||
('draft', 'Quotation'),
|
||||
('sent', 'Quotation Sent'),
|
||||
('waiting_date', 'Waiting Schedule'),
|
||||
('manual', 'Manual In Progress'),
|
||||
('progress', 'In Progress'),
|
||||
|
||||
@@ -138,20 +138,21 @@ class product_product(osv.osv):
|
||||
domain_move_in += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_products
|
||||
domain_move_out += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_products
|
||||
domain_quant += domain_products
|
||||
if context.get('lot_id') or context.get('owner_id') or context.get('package_id'):
|
||||
if context.get('lot_id'):
|
||||
domain_quant.append(('lot_id', '=', context['lot_id']))
|
||||
if context.get('owner_id'):
|
||||
domain_quant.append(('owner_id', '=', context['owner_id']))
|
||||
if context.get('package_id'):
|
||||
domain_quant.append(('package_id', '=', context['package_id']))
|
||||
moves_in = []
|
||||
moves_out = []
|
||||
else:
|
||||
domain_move_in += domain_move_in_loc
|
||||
domain_move_out += domain_move_out_loc
|
||||
moves_in = self.pool.get('stock.move').read_group(cr, uid, domain_move_in, ['product_id', 'product_qty'], ['product_id'], context=context)
|
||||
moves_out = self.pool.get('stock.move').read_group(cr, uid, domain_move_out, ['product_id', 'product_qty'], ['product_id'], context=context)
|
||||
|
||||
if context.get('lot_id'):
|
||||
domain_quant.append(('lot_id', '=', context['lot_id']))
|
||||
if context.get('owner_id'):
|
||||
domain_quant.append(('owner_id', '=', context['owner_id']))
|
||||
owner_domain = ('restrict_partner_id', '=', context['owner_id'])
|
||||
domain_move_in.append(owner_domain)
|
||||
domain_move_out.append(owner_domain)
|
||||
if context.get('package_id'):
|
||||
domain_quant.append(('package_id', '=', context['package_id']))
|
||||
|
||||
domain_move_in += domain_move_in_loc
|
||||
domain_move_out += domain_move_out_loc
|
||||
moves_in = self.pool.get('stock.move').read_group(cr, uid, domain_move_in, ['product_id', 'product_qty'], ['product_id'], context=context)
|
||||
moves_out = self.pool.get('stock.move').read_group(cr, uid, domain_move_out, ['product_id', 'product_qty'], ['product_id'], context=context)
|
||||
|
||||
domain_quant += domain_quant_loc
|
||||
quants = self.pool.get('stock.quant').read_group(cr, uid, domain_quant, ['product_id', 'qty'], ['product_id'], context=context)
|
||||
|
||||
@@ -1053,7 +1053,7 @@ class stock_picking(osv.osv):
|
||||
|
||||
# If we encounter an UoM that is smaller than the default UoM or the one already chosen, use the new one instead.
|
||||
product_uom = {} # Determines UoM used in pack operations
|
||||
for move in picking.move_lines:
|
||||
for move in [x for x in picking.move_lines if x.state not in ('done', 'cancel')]:
|
||||
if not product_uom.get(move.product_id.id):
|
||||
product_uom[move.product_id.id] = move.product_id.uom_id
|
||||
if move.product_uom.id != move.product_id.uom_id.id and move.product_uom.factor > product_uom[move.product_id.id].factor:
|
||||
@@ -1136,7 +1136,7 @@ class stock_picking(osv.osv):
|
||||
prevals[key[0]] = [val_dict]
|
||||
# prevals var holds the operations in order to create them in the same order than the picking stock moves if possible
|
||||
processed_products = set()
|
||||
for move in picking.move_lines:
|
||||
for move in [x for x in picking.move_lines if x.state not in ('done', 'cancel')]:
|
||||
if move.product_id.id not in processed_products:
|
||||
vals += prevals.get(move.product_id.id, [])
|
||||
processed_products.add(move.product_id.id)
|
||||
@@ -1247,7 +1247,7 @@ class stock_picking(osv.osv):
|
||||
prod2move_ids = {}
|
||||
still_to_do = []
|
||||
#make a dictionary giving for each product, the moves and related quantity that can be used in operation links
|
||||
for move in picking.move_lines:
|
||||
for move in [x for x in picking.move_lines if x.state not in ('done', 'cancel')]:
|
||||
if not prod2move_ids.get(move.product_id.id):
|
||||
prod2move_ids[move.product_id.id] = [{'move': move, 'remaining_qty': move.product_qty}]
|
||||
else:
|
||||
@@ -2532,7 +2532,7 @@ class stock_move(models.Model):
|
||||
}
|
||||
if context.get('source_location_id'):
|
||||
defaults['location_id'] = context['source_location_id']
|
||||
new_move = self.copy(cr, uid, move.id, defaults)
|
||||
new_move = self.copy(cr, uid, move.id, defaults, context=context)
|
||||
|
||||
ctx = context.copy()
|
||||
ctx['do_not_propagate'] = True
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_stock_flow
|
||||
from . import test_owner_available
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# Author: Leonardo Pistone
|
||||
# Copyright 2015 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
from openerp.addons.stock.tests.common import TestStockCommon
|
||||
|
||||
|
||||
class TestVirtualAvailable(TestStockCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVirtualAvailable, self).setUp()
|
||||
|
||||
self.env['stock.quant'].create({
|
||||
'product_id': self.productA.id,
|
||||
'location_id': self.stock_location,
|
||||
'qty': 30.0,
|
||||
})
|
||||
|
||||
self.env['stock.quant'].create({
|
||||
'product_id': self.productA.id,
|
||||
'location_id': self.stock_location,
|
||||
'qty': 10.0,
|
||||
'owner_id': self.ref('base.res_partner_4'),
|
||||
})
|
||||
|
||||
self.picking_out = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.ref('stock.picking_type_out')})
|
||||
self.env['stock.move'].create({
|
||||
'name': 'a move',
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_qty': 3.0,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'picking_id': self.picking_out.id,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
|
||||
self.picking_out_2 = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.ref('stock.picking_type_out')})
|
||||
self.env['stock.move'].create({
|
||||
'restrict_partner_id': self.ref('base.res_partner_4'),
|
||||
'name': 'another move',
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_qty': 5.0,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'picking_id': self.picking_out_2.id,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
|
||||
|
||||
def test_without_owner(self):
|
||||
self.assertAlmostEqual(40.0, self.productA.virtual_available)
|
||||
self.picking_out.action_assign()
|
||||
self.picking_out_2.action_assign()
|
||||
|
||||
self.assertAlmostEqual(32.0, self.productA.virtual_available)
|
||||
|
||||
def test_with_owner(self):
|
||||
prod_context = self.productA.with_context(
|
||||
owner_id=self.ref('base.res_partner_4')
|
||||
)
|
||||
self.assertAlmostEqual(10.0, prod_context.virtual_available)
|
||||
self.picking_out.action_assign()
|
||||
self.picking_out_2.action_assign()
|
||||
self.assertAlmostEqual(5.0, prod_context.virtual_available)
|
||||
@@ -48,8 +48,7 @@ class stock_transfer_details(models.TransientModel):
|
||||
picking = self.pool.get('stock.picking').browse(cr, uid, picking_id, context=context)
|
||||
items = []
|
||||
packs = []
|
||||
if not picking.pack_operation_ids:
|
||||
picking.do_prepare_partial()
|
||||
picking.do_prepare_partial()
|
||||
for op in picking.pack_operation_ids:
|
||||
item = {
|
||||
'packop_id': op.id,
|
||||
|
||||
@@ -245,11 +245,11 @@ class stock_quant(osv.osv):
|
||||
move_obj = self.pool.get('account.move')
|
||||
for cost, qty in quant_cost_qty.items():
|
||||
move_lines = self._prepare_account_move_line(cr, uid, move, qty, cost, credit_account_id, debit_account_id, context=context)
|
||||
period_id = context.get('force_period', self.pool.get('account.period').find(cr, uid, move.date, context=context)[0])
|
||||
period_id = context.get('force_period', self.pool.get('account.period').find(cr, uid, context=context)[0])
|
||||
move_obj.create(cr, uid, {'journal_id': journal_id,
|
||||
'line_id': move_lines,
|
||||
'period_id': period_id,
|
||||
'date': move.date,
|
||||
'date': fields.date.context_today(self, cr, uid, context=context),
|
||||
'ref': move.picking_id.name}, context=context)
|
||||
|
||||
#def _reconcile_single_negative_quant(self, cr, uid, to_solve_quant, quant, quant_neg, qty, context=None):
|
||||
|
||||
@@ -91,7 +91,7 @@ class stock_landed_cost(osv.osv):
|
||||
),
|
||||
'state': fields.selection([('draft', 'Draft'), ('done', 'Posted'), ('cancel', 'Cancelled')], 'State', readonly=True, track_visibility='onchange', copy=False),
|
||||
'account_move_id': fields.many2one('account.move', 'Journal Entry', readonly=True, copy=False),
|
||||
'account_journal_id': fields.many2one('account.journal', 'Account Journal', required=True),
|
||||
'account_journal_id': fields.many2one('account.journal', 'Account Journal', required=True, states={'done': [('readonly', True)]}),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
@@ -121,41 +121,43 @@ class stock_landed_cost(osv.osv):
|
||||
Afterwards, for the goods that are already out of stock, we should create the out moves
|
||||
"""
|
||||
aml_obj = self.pool.get('account.move.line')
|
||||
aml_obj.create(cr, uid, {
|
||||
base_line = {
|
||||
'name': line.name,
|
||||
'move_id': move_id,
|
||||
'product_id': line.product_id.id,
|
||||
'quantity': line.quantity,
|
||||
'debit': line.additional_landed_cost,
|
||||
'account_id': debit_account_id
|
||||
}, context=context)
|
||||
aml_obj.create(cr, uid, {
|
||||
'name': line.name,
|
||||
'move_id': move_id,
|
||||
'product_id': line.product_id.id,
|
||||
'quantity': line.quantity,
|
||||
'credit': line.additional_landed_cost,
|
||||
'account_id': credit_account_id
|
||||
}, context=context)
|
||||
}
|
||||
debit_line = dict(base_line, account_id=debit_account_id)
|
||||
credit_line = dict(base_line, account_id=credit_account_id)
|
||||
diff = line.additional_landed_cost
|
||||
if diff > 0:
|
||||
debit_line['debit'] = diff
|
||||
credit_line['credit'] = diff
|
||||
else:
|
||||
# negative cost, reverse the entry
|
||||
debit_line['credit'] = -diff
|
||||
credit_line['debit'] = -diff
|
||||
aml_obj.create(cr, uid, debit_line, context=context)
|
||||
aml_obj.create(cr, uid, credit_line, context=context)
|
||||
|
||||
#Create account move lines for quants already out of stock
|
||||
if qty_out > 0:
|
||||
aml_obj.create(cr, uid, {
|
||||
'name': line.name + ": " + str(qty_out) + _(' already out'),
|
||||
'move_id': move_id,
|
||||
'product_id': line.product_id.id,
|
||||
'quantity': qty_out,
|
||||
'credit': line.additional_landed_cost * qty_out / line.quantity,
|
||||
'account_id': debit_account_id
|
||||
}, context=context)
|
||||
aml_obj.create(cr, uid, {
|
||||
'name': line.name + ": " + str(qty_out) + _(' already out'),
|
||||
'move_id': move_id,
|
||||
'product_id': line.product_id.id,
|
||||
'quantity': qty_out,
|
||||
'debit': line.additional_landed_cost * qty_out / line.quantity,
|
||||
'account_id': already_out_account_id
|
||||
}, context=context)
|
||||
debit_line = dict(debit_line,
|
||||
name=(line.name + ": " + str(qty_out) + _(' already out')),
|
||||
quantity=qty_out)
|
||||
credit_line = dict(credit_line,
|
||||
name=(line.name + ": " + str(qty_out) + _(' already out')),
|
||||
quantity=qty_out)
|
||||
diff = diff * qty_out / line.quantity
|
||||
if diff > 0:
|
||||
debit_line['debit'] = diff
|
||||
credit_line['credit'] = diff
|
||||
else:
|
||||
# negative cost, reverse the entry
|
||||
debit_line['credit'] = -diff
|
||||
credit_line['debit'] = -diff
|
||||
aml_obj.create(cr, uid, debit_line, context=context)
|
||||
aml_obj.create(cr, uid, credit_line, context=context)
|
||||
return True
|
||||
|
||||
def _create_account_move(self, cr, uid, cost, context=None):
|
||||
@@ -193,6 +195,8 @@ class stock_landed_cost(osv.osv):
|
||||
quant_obj = self.pool.get('stock.quant')
|
||||
|
||||
for cost in self.browse(cr, uid, ids, context=context):
|
||||
if cost.state != 'draft':
|
||||
raise UserError(_('Only draft landed costs can be validated'))
|
||||
if not cost.valuation_adjustment_lines or not self._check_sum(cr, uid, cost, context=context):
|
||||
raise UserError(_('You cannot validate a landed cost which has no valid valuation lines.'))
|
||||
move_id = self._create_account_move(cr, uid, cost, context=context)
|
||||
@@ -209,6 +213,7 @@ class stock_landed_cost(osv.osv):
|
||||
else:
|
||||
quant_dict[quant.id] += diff
|
||||
for key, value in quant_dict.items():
|
||||
print value
|
||||
quant_obj.write(cr, uid, key, {'cost': value}, context=context)
|
||||
qty_out = 0
|
||||
for quant in line.move_id.quant_ids:
|
||||
@@ -219,8 +224,16 @@ class stock_landed_cost(osv.osv):
|
||||
return True
|
||||
|
||||
def button_cancel(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
|
||||
return True
|
||||
cost = self.browse(cr, uid, ids, context=context)
|
||||
if cost.state == 'done':
|
||||
raise UserError(_('Validated landed costs cannot be cancelled, '
|
||||
'but you could create negative landed costs to reverse them'))
|
||||
return cost.write({'state': 'cancel'})
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# cancel or raise first
|
||||
self.button_cancel(cr, uid, ids, context)
|
||||
return super(stock_landed_cost, self).unlink(cr, uid, ids, context=context)
|
||||
|
||||
def compute_landed_cost(self, cr, uid, ids, context=None):
|
||||
line_obj = self.pool.get('stock.valuation.adjustment.lines')
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<group>
|
||||
<separator string="Valuation Adjustments"/>
|
||||
</group>
|
||||
<group>
|
||||
<group states='draft'>
|
||||
<button name="compute_landed_cost" string="Compute" type="object" class="oe_right"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
@@ -534,14 +534,15 @@ class Home(http.Controller):
|
||||
@http.route([
|
||||
'/web/css/<xmlid>',
|
||||
'/web/css/<xmlid>/<version>',
|
||||
'/web/css.<int:page>/<xmlid>/<version>',
|
||||
], type='http', auth='public')
|
||||
def css_bundle(self, xmlid, version=None, **kw):
|
||||
def css_bundle(self, xmlid, version=None, page=None, **kw):
|
||||
try:
|
||||
bundle = AssetsBundle(xmlid)
|
||||
except QWebTemplateNotFound:
|
||||
return request.not_found()
|
||||
|
||||
response = request.make_response(bundle.css(), [('Content-Type', 'text/css')])
|
||||
response = request.make_response(bundle.css(page), [('Content-Type', 'text/css')])
|
||||
return make_conditional(response, bundle.last_modified, max_age=BUNDLE_MAXAGE)
|
||||
|
||||
class WebClient(http.Controller):
|
||||
|
||||
@@ -479,9 +479,13 @@ openerp.PropertiesMixin = _.extend({}, openerp.EventDispatcherMixin, {
|
||||
if (tmp === val)
|
||||
return;
|
||||
if (key === 'value' && self.field && self.field.type === 'float' && tmp && val){
|
||||
var precision = self.field.digits ? self.field.digits[1] : 2;
|
||||
if (openerp.web.float_is_zero(tmp - val, precision))
|
||||
return;
|
||||
var digits = self.field.digits;
|
||||
if (digits !== 0){
|
||||
digits = digits ? digits[1] : 2;
|
||||
if (openerp.web.float_is_zero(tmp - val, digits)){
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
self.__getterSetterInternalMap[key] = val;
|
||||
|
||||
@@ -157,6 +157,9 @@
|
||||
position: relative;
|
||||
opacity: 0.75;
|
||||
top: 26px; }
|
||||
:root .openerp .oe_kanban_view .oe_kanban_group_title_vertical {
|
||||
writing-mode: lr-tb\9;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_add, .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle {
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -859,7 +859,7 @@ function getColor(val, pct, col, noGradient, custSec) {
|
||||
|
||||
/** Fix Raphael display:none tspan dy attribute bug */
|
||||
function setDy(elem, fontSize, txtYpos) {
|
||||
if ((!ie || ie > 9) && elem.node.firstChild.attributes.dy) {
|
||||
if ((!ie || ie > 8) && elem.node.firstChild.attributes.dy) {
|
||||
elem.node.firstChild.attributes.dy.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,15 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
return http.redirect_with_hash(redirect)
|
||||
return r
|
||||
|
||||
@http.route('/website/lang/<lang>', type='http', auth="public", website=True, multilang=False)
|
||||
def change_lang(self, lang, r='/', **kwargs):
|
||||
if lang == 'default':
|
||||
lang = request.website.default_lang_code
|
||||
r = '/%s%s' % (lang, r or '/')
|
||||
redirect = werkzeug.utils.redirect(r or ('/%s' % lang), 303)
|
||||
redirect.set_cookie('website_lang', lang)
|
||||
return redirect
|
||||
|
||||
@http.route('/page/<page:page>', type='http', auth="public", website=True)
|
||||
def page(self, page, **opt):
|
||||
values = {
|
||||
@@ -225,7 +234,6 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
return request.registry["ir.ui.view"].customize_template_get(
|
||||
request.cr, request.uid, key, full=full, bundles=bundles,
|
||||
context=request.context)
|
||||
|
||||
@http.route('/website/get_view_translations', type='json', auth='public', website=True)
|
||||
def get_view_translations(self, xml_id, lang=None):
|
||||
lang = lang or request.context.get('lang')
|
||||
@@ -290,7 +298,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
uploads.append({'website_url': url})
|
||||
name = url.split("/").pop() # recover filename
|
||||
attachment_id = Attachments.create(request.cr, request.uid, {
|
||||
'name':name,
|
||||
'name': name,
|
||||
'type': 'url',
|
||||
'url': url,
|
||||
'res_model': 'ir.ui.view',
|
||||
|
||||
@@ -45,6 +45,16 @@ class ir_http(orm.AbstractModel):
|
||||
else:
|
||||
request.uid = request.session.uid
|
||||
|
||||
bots = "bot|crawl|slurp|spider|curl|wget".split("|")
|
||||
def is_a_bot(self):
|
||||
# We don't use regexp and ustr voluntarily
|
||||
# timeit has been done to check the optimum method
|
||||
ua = request.httprequest.environ.get('HTTP_USER_AGENT', '').lower()
|
||||
try:
|
||||
return any(bot in ua for bot in self.bots)
|
||||
except UnicodeDecodeError:
|
||||
return any(bot in ua.encode('ascii', 'ignore') for bot in self.bots)
|
||||
|
||||
def _dispatch(self):
|
||||
first_pass = not hasattr(request, 'website')
|
||||
request.website = None
|
||||
@@ -57,7 +67,10 @@ class ir_http(orm.AbstractModel):
|
||||
# in all cases, website processes them
|
||||
request.website_enabled = True
|
||||
|
||||
request.website_multilang = request.website_enabled and func and func.routing.get('multilang', True)
|
||||
request.website_multilang = (
|
||||
request.website_enabled and
|
||||
func and func.routing.get('multilang', func.routing['type'] == 'http')
|
||||
)
|
||||
|
||||
if 'geoip' not in request.session:
|
||||
record = {}
|
||||
@@ -78,6 +91,7 @@ class ir_http(orm.AbstractModel):
|
||||
record = self.geo_ip_resolver.record_by_addr(request.httprequest.remote_addr) or {}
|
||||
request.session['geoip'] = record
|
||||
|
||||
cook_lang = request.httprequest.cookies.get('website_lang')
|
||||
if request.website_enabled:
|
||||
try:
|
||||
if func:
|
||||
@@ -92,10 +106,13 @@ class ir_http(orm.AbstractModel):
|
||||
request.context['website_id'] = request.website.id
|
||||
langs = [lg[0] for lg in request.website.get_languages()]
|
||||
path = request.httprequest.path.split('/')
|
||||
|
||||
if first_pass:
|
||||
if request.website_multilang:
|
||||
is_a_bot = self.is_a_bot()
|
||||
# If the url doesn't contains the lang and that it's the first connection, we to retreive the user preference if it exists.
|
||||
if not path[1] in langs and not request.httprequest.cookies.get('session_id'):
|
||||
if not path[1] in langs and not is_a_bot:
|
||||
request.lang = cook_lang or request.lang
|
||||
if request.lang not in langs:
|
||||
# Try to find a similar lang. Eg: fr_BE and fr_FR
|
||||
short = request.lang.split('_')[0]
|
||||
@@ -104,14 +121,16 @@ class ir_http(orm.AbstractModel):
|
||||
request.lang = langs_withshort[0]
|
||||
else:
|
||||
request.lang = request.website.default_lang_code
|
||||
# We redirect with the right language in url
|
||||
if request.lang != request.website.default_lang_code:
|
||||
path.insert(1, request.lang)
|
||||
path = '/'.join(path) or '/'
|
||||
return request.redirect(path + '?' + request.httprequest.query_string)
|
||||
else:
|
||||
request.lang = request.website.default_lang_code
|
||||
|
||||
if request.lang != request.website.default_lang_code:
|
||||
path.insert(1, request.lang)
|
||||
path = '/'.join(path) or '/'
|
||||
redirect = request.redirect(path + '?' + request.httprequest.query_string)
|
||||
redirect.set_cookie('website_lang', request.lang)
|
||||
return redirect
|
||||
|
||||
request.context['lang'] = request.lang
|
||||
if not request.context.get('tz'):
|
||||
request.context['tz'] = request.session['geoip'].get('time_zone')
|
||||
@@ -122,11 +141,17 @@ class ir_http(orm.AbstractModel):
|
||||
if request.lang == request.website.default_lang_code:
|
||||
# If language is in the url and it is the default language, redirect
|
||||
# to url without language so google doesn't see duplicate content
|
||||
return request.redirect(path + '?' + request.httprequest.query_string, code=301)
|
||||
resp = request.redirect(path + '?' + request.httprequest.query_string, code=301)
|
||||
if cook_lang != request.lang: # If default lang setted in url directly
|
||||
resp.set_cookie('website_lang', request.lang)
|
||||
return resp
|
||||
return self.reroute(path)
|
||||
# bind modified context
|
||||
request.website = request.website.with_context(request.context)
|
||||
return super(ir_http, self)._dispatch()
|
||||
resp = super(ir_http, self)._dispatch()
|
||||
if not cook_lang:
|
||||
resp.set_cookie('website_lang', request.lang)
|
||||
return resp
|
||||
|
||||
def reroute(self, path):
|
||||
if not hasattr(request, 'rerouting'):
|
||||
|
||||
@@ -80,7 +80,8 @@ def is_multilang_url(local_url, langs=None):
|
||||
router = request.httprequest.app.get_db_router(request.db).bind('')
|
||||
# Force to check method to POST. Odoo uses methods : ['POST'] and ['GET', 'POST']
|
||||
func = router.match(path, method='POST', query_args=query_string)[0]
|
||||
return func.routing.get('website', False) and func.routing.get('multilang', True)
|
||||
return (func.routing.get('website', False) and
|
||||
func.routing.get('multilang', func.routing['type'] == 'http'))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@@ -351,6 +351,30 @@
|
||||
});
|
||||
});
|
||||
|
||||
if (!$('.js_change_lang').length) {
|
||||
// in case template is not up to date...
|
||||
var links = $('ul.js_language_selector li a:not([data-oe-id])');
|
||||
var m = $(_.min(links, function(l) { return $(l).attr('href').length; })).attr('href');
|
||||
links.each(function() {
|
||||
var t = $(this).attr('href');
|
||||
var l = (t === m) ? "default" : t.split('/')[1];
|
||||
$(this).data('lang', l).addClass('js_change_lang');
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('click', '.js_change_lang', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var self = $(this);
|
||||
// retrieve the hash before the redirect
|
||||
var redirect = {
|
||||
lang: self.data('lang'),
|
||||
url: self.attr('href'),
|
||||
hash: location.hash
|
||||
};
|
||||
location.href = _.str.sprintf("/website/lang/%(lang)s?r=%(url)s%(hash)s", redirect);
|
||||
});
|
||||
|
||||
/* ----- KANBAN WEBSITE ---- */
|
||||
$('.js_kanban').each(function () {
|
||||
website.init_kanban(this);
|
||||
|
||||
@@ -29,15 +29,15 @@
|
||||
|
||||
<template id="website.submenu" name="Submenu">
|
||||
<li t-if="not submenu.child_id" t-att-class="
|
||||
((submenu.url != '/' and request.httprequest.path.startswith(submenu.url)) or
|
||||
((submenu.url and submenu.url != '/' and request.httprequest.path.startswith(submenu.url)) or
|
||||
request.httprequest.path == submenu.url) and 'active'
|
||||
">
|
||||
<a t-att-href="(website.menu_id.child_id[0] == submenu) and '/' or submenu.url" t-ignore="true" t-att-target="'blank' if submenu.new_window else None">
|
||||
<a t-att-href="(website.menu_id.child_id[0] == submenu) and '/' or submenu.url" t-ignore="true" t-att-target="'_blank' if submenu.new_window else None">
|
||||
<span t-field="submenu.name"/>
|
||||
</a>
|
||||
</li>
|
||||
<li t-if="submenu.child_id" t-attf-class="dropdown #{
|
||||
((submenu.url != '/' and [1 for submenu in submenu.child_id if request.httprequest.path.startswith(submenu.url)]) or
|
||||
((submenu.url and submenu.url != '/' and [1 for submenu in submenu.child_id if request.httprequest.path.startswith(submenu.url)]) or
|
||||
request.httprequest.path == submenu.url) and 'active'
|
||||
}">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
@@ -394,10 +394,14 @@
|
||||
<template id="language_selector">
|
||||
<ul class="list-inline js_language_selector mt16" t-if="(request and request.website_multilang and len(languages) > 1) or (website and editable)">
|
||||
<li t-foreach="languages" t-as="lg">
|
||||
<a t-att-href="url_for(request.httprequest.path + '?' + keep_query(), lang=lg[0])"
|
||||
t-att-data-default-lang="editable and 'true' if website and lg[0] == website.default_lang_code else None">
|
||||
<t t-esc="lg[1].split('/').pop()"/>
|
||||
</a>
|
||||
<a
|
||||
t-att-href="url_for(request.httprequest.path + '?' + keep_query(), lang=lg[0])"
|
||||
t-att-data-default-lang="editable and 'true' if website and lg[0] == website.default_lang_code else None"
|
||||
t-att-data-lang="lg[0]"
|
||||
class="js_change_lang"
|
||||
>
|
||||
<t t-esc="lg[1].split('/').pop()"/>
|
||||
</a>
|
||||
</li>
|
||||
<li groups="base.group_website_publisher">
|
||||
<t t-set="url_return" t-value="url_for('', '[lang]') + '?' + keep_query()"/>
|
||||
@@ -513,6 +517,13 @@
|
||||
<template id="info" name="Odoo Information">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="show_website_info" inherit_id="website.info" customize_show="True" name="Show Odoo Information">
|
||||
<xpath expr="//div[@id='wrap']" position="inside">
|
||||
|
||||
<div class="oe_structure">
|
||||
|
||||
<section class="container">
|
||||
@@ -539,35 +550,37 @@
|
||||
</dt>
|
||||
<dd>
|
||||
<span t-raw="app.summary"/>
|
||||
</dd><dd class="text-muted">
|
||||
</dd><dd class="text-muted" groups='base.group_no_one'>
|
||||
Technical name: <span t-field="app.name"/>, updated: <span t-field="app.write_date"/>, author: <span t-field="app.author"/>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<h2>Installed Modules</h2>
|
||||
<dl class="dl-horizontal">
|
||||
<t t-foreach="modules" t-as="app">
|
||||
<dt>
|
||||
<span t-raw="app.shortdesc"/>
|
||||
</dt>
|
||||
<dd t-if="app.summary">
|
||||
<span t-raw="app.summary"/>
|
||||
</dd><dd>
|
||||
Technical name: <span t-field="app.name"/>,
|
||||
updated: <span t-field="app.write_date"/>
|
||||
<t t-if="app.author">
|
||||
, author: <span t-field="app.author"/>
|
||||
</t>
|
||||
</dd>
|
||||
</t>
|
||||
</dl>
|
||||
<div groups='base.group_no_one'>
|
||||
<h2>Installed Modules</h2>
|
||||
<dl class="dl-horizontal">
|
||||
<t t-foreach="modules" t-as="app">
|
||||
<dt>
|
||||
<span t-raw="app.shortdesc"/>
|
||||
</dt>
|
||||
<dd t-if="app.summary">
|
||||
<span t-raw="app.summary"/>
|
||||
</dd><dd>
|
||||
Technical name: <span t-field="app.name"/>,
|
||||
updated: <span t-field="app.write_date"/>
|
||||
<t t-if="app.author">
|
||||
, author: <span t-field="app.author"/>
|
||||
</t>
|
||||
</dd>
|
||||
</t>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="default_page">
|
||||
|
||||
@@ -365,6 +365,14 @@ class Post(models.Model):
|
||||
self.env.user.sudo().add_karma(post.forum_id.karma_gen_question_new)
|
||||
return post
|
||||
|
||||
@api.multi
|
||||
def check_mail_message_access(self, operation, model_obj=None):
|
||||
for post in self:
|
||||
# Make sure only author or moderator can edit/delete messages
|
||||
if operation in ('write', 'unlink') and not post.can_edit:
|
||||
raise KarmaError('Not enough karma to edit a post.')
|
||||
return super(Post, self).check_mail_message_access(operation, model_obj=model_obj)
|
||||
|
||||
@api.multi
|
||||
@api.depends('name', 'post_type')
|
||||
def name_get(self):
|
||||
|
||||
@@ -978,3 +978,12 @@ class website_sale(http.Controller):
|
||||
}
|
||||
ret['lines'] = self.order_lines_2_google_api(order.order_line)
|
||||
return ret
|
||||
|
||||
@http.route(['/shop/get_unit_price'], type='json', auth="public", methods=['POST'], website=True)
|
||||
def get_unit_price(self, product_ids, add_qty, **kw):
|
||||
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
|
||||
products = pool['product.product'].browse(cr, uid, product_ids, context=context)
|
||||
partner = pool['res.users'].browse(cr, uid, uid, context=context).partner_id
|
||||
pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id
|
||||
prices = pool['product.pricelist'].price_rule_get_multi(cr, uid, [], [(product, add_qty, partner) for product in products], context=context)
|
||||
return {product_id: prices[product_id][pricelist_id][0] for product_id in product_ids}
|
||||
|
||||
@@ -19,17 +19,56 @@ $('.oe_website_sale').each(function () {
|
||||
});
|
||||
});
|
||||
|
||||
$(oe_website_sale).on("change", 'input[name="add_qty"]', function (event) {
|
||||
product_ids = [];
|
||||
var product_dom = $("ul.js_add_cart_variants[data-attribute_value_ids]").first();
|
||||
product_dom.data("attribute_value_ids").forEach(function(entry) {
|
||||
product_ids.push(entry[0]);});
|
||||
var qty = $(event.target).closest('form').find('input[name="add_qty"]').val();
|
||||
|
||||
openerp.jsonRpc("/shop/get_unit_price", 'call', {'product_ids': product_ids,'add_qty': parseInt(qty)})
|
||||
.then(function (data) {
|
||||
var current = product_dom.data("attribute_value_ids");
|
||||
for(var j=0; j < current.length; j++){
|
||||
current[j][2] = data[current[j][0]];
|
||||
}
|
||||
product_dom.attr("data-attribute_value_ids", JSON.stringify(current)).trigger("change");
|
||||
});
|
||||
});
|
||||
|
||||
// change for css
|
||||
$(oe_website_sale).on('mouseup touchend', '.js_publish', function (ev) {
|
||||
$(ev.currentTarget).parents(".thumbnail").toggleClass("disabled");
|
||||
});
|
||||
|
||||
$(oe_website_sale).on("change", ".oe_cart input.js_quantity", function () {
|
||||
$(oe_website_sale).on("change", ".oe_cart input.js_quantity", function (event) {
|
||||
var $input = $(this);
|
||||
var value = parseInt($input.val(), 10);
|
||||
var $dom = $(event.target).closest('tr');
|
||||
var default_price = parseFloat($dom.find('.text-danger > span.oe_currency_value').text());
|
||||
var $dom_optional = $dom.nextUntil(':not(.optional_product.info)');
|
||||
var line_id = parseInt($input.data('line-id'),10);
|
||||
var product_id = parseInt($input.data('product-id'),10);
|
||||
var product_ids = [product_id];
|
||||
$dom_optional.each(function(){
|
||||
product_ids.push($(this).find('span[data-oe-model="product.product"]').data('oe-id'));
|
||||
});
|
||||
if (isNaN(value)) value = 0;
|
||||
openerp.jsonRpc("/shop/cart/update_json", 'call', {
|
||||
openerp.jsonRpc("/shop/get_unit_price", 'call', {
|
||||
'product_ids': product_ids,
|
||||
'add_qty': value})
|
||||
.then(function (res) {
|
||||
//basic case
|
||||
$dom.find('span.oe_currency_value').last().text(res[product_id].toFixed(2));
|
||||
$dom.find('.text-danger').toggle(res[product_id]<default_price && (default_price-res[product_id] > default_price/100));
|
||||
//optional case
|
||||
$dom_optional.each(function(){
|
||||
var id = $(this).find('span[data-oe-model="product.product"]').data('oe-id');
|
||||
var price = parseFloat($(this).find(".text-danger > span.oe_currency_value").text());
|
||||
$(this).find("span.oe_currency_value").last().text(res[id].toFixed(2));
|
||||
$(this).find('.text-danger').toggle(res[id]<price && (price-res[id]>price/100));
|
||||
});
|
||||
openerp.jsonRpc("/shop/cart/update_json", 'call', {
|
||||
'line_id': line_id,
|
||||
'product_id': parseInt($input.data('product-id'),10),
|
||||
'set_qty': value})
|
||||
@@ -57,6 +96,7 @@ $('.oe_website_sale').each(function () {
|
||||
$input.val(data.quantity);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// hack to add and rome from cart with json
|
||||
@@ -112,12 +152,13 @@ $('.oe_website_sale').each(function () {
|
||||
$img.attr("src", "/website/image/product.product/" + $(this).val() + "/image");
|
||||
});
|
||||
|
||||
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change', function (ev) {
|
||||
var $ul = $(this).parents('ul.js_add_cart_variants:first');
|
||||
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change, ul[data-attribute_value_ids]', function (ev) {
|
||||
var $ul = $(ev.target).closest('ul.js_add_cart_variants');
|
||||
var $parent = $ul.closest('.js_product');
|
||||
var $product_id = $parent.find('input.product_id').first();
|
||||
var $price = $parent.find(".oe_price:first .oe_currency_value");
|
||||
var $default_price = $parent.find(".oe_default_price:first .oe_currency_value");
|
||||
var $optional_price = $parent.find(".oe_optional:first .oe_currency_value");
|
||||
var variant_ids = $ul.data("attribute_value_ids");
|
||||
var values = [];
|
||||
$parent.find('input.js_variant_change:checked, select.js_variant_change').each(function () {
|
||||
@@ -133,8 +174,10 @@ $('.oe_website_sale').each(function () {
|
||||
$default_price.html(price_to_str(variant_ids[k][3]));
|
||||
if (variant_ids[k][3]-variant_ids[k][2]>0.2) {
|
||||
$default_price.closest('.oe_website_sale').addClass("discount");
|
||||
$optional_price.closest('.oe_optional').show().css('text-decoration', 'line-through');
|
||||
} else {
|
||||
$default_price.closest('.oe_website_sale').removeClass("discount");
|
||||
$optional_price.closest('.oe_optional').hide();
|
||||
}
|
||||
product_id = variant_ids[k][0];
|
||||
break;
|
||||
|
||||
@@ -619,16 +619,15 @@
|
||||
<div class="text-muted" t-esc="'\n'.join(line.name.splitlines()[1:])"/>
|
||||
</td>
|
||||
<td class="text-center" name="price">
|
||||
<t t-if="(compute_currency(line.product_id.lst_price) - line.price_unit) > 1">
|
||||
<del class="text-danger" style="white-space: nowrap;"
|
||||
<del class="text-danger" t-att-style="'' if (compute_currency(line.product_id.lst_price) - line.price_unit) > 1 else 'display: none;'" style="text-decoration: line-through; white-space: nowrap;"
|
||||
t-field="line.product_id.lst_price" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "line.company_id.currency_id",
|
||||
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
|
||||
}'/>&nbsp;
|
||||
</t>
|
||||
<span t-field="line.price_unit" style="white-space: nowrap;" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "website_sale_order.pricelist_id.currency_id",
|
||||
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
|
||||
}'/>
|
||||
</td>
|
||||
@@ -917,16 +916,19 @@
|
||||
<div class="col-sm-6 text-right">Subtotal: </div>
|
||||
<div class="col-sm-6"><span style="white-space: nowrap;" t-field="website_sale_order.amount_untaxed" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "website_sale_order.pricelist_id.currency_id",
|
||||
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
|
||||
}'/></div>
|
||||
<div class="col-sm-6 text-right">Taxes: </div>
|
||||
<div class="col-sm-6"><span style="white-space: nowrap;" t-field="website_sale_order.amount_tax" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "website_sale_order.pricelist_id.currency_id",
|
||||
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
|
||||
}'/></div>
|
||||
<div class="col-sm-6 text-right"><h4>Total To Pay: </h4></div>
|
||||
<div class="col-sm-6"><h4><span style="white-space: nowrap;" t-field="website_sale_order.amount_total" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "website_sale_order.pricelist_id.currency_id",
|
||||
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
|
||||
}'/></h4></div>
|
||||
</div>
|
||||
@@ -986,6 +988,7 @@
|
||||
<td class="text-center">
|
||||
<span t-field="line.price_unit" style="white-space: nowrap;" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "website_sale_order.pricelist_id.currency_id",
|
||||
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
|
||||
}'/>
|
||||
</td>
|
||||
@@ -1201,6 +1204,7 @@
|
||||
<th class="text-right">
|
||||
<h3><span t-field="website_sale_order.amount_total" style="white-space: nowrap;" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "website_sale_order.pricelist_id.currency_id",
|
||||
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
|
||||
}'/></h3>
|
||||
</th>
|
||||
@@ -1210,6 +1214,7 @@
|
||||
<td class="text-right">
|
||||
<span t-field="website_sale_order.amount_tax" style="white-space: nowrap;" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "website_sale_order.pricelist_id.currency_id",
|
||||
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
|
||||
}'/>
|
||||
</td>
|
||||
|
||||
@@ -22,7 +22,7 @@ class website_sale_options(website_sale):
|
||||
r.qcontext['optional_product_ids'] = optional_product_ids
|
||||
return r
|
||||
|
||||
@http.route(['/shop/cart/update_option'], type='http', auth="public", methods=['POST'], website=True)
|
||||
@http.route(['/shop/cart/update_option'], type='http', auth="public", methods=['POST'], website=True, multilang=False)
|
||||
def cart_options_update_json(self, product_id, add_qty=1, set_qty=0, goto_shop=None, lang=None, **kw):
|
||||
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
|
||||
if lang:
|
||||
|
||||
@@ -48,13 +48,11 @@ class sale_order(osv.Model):
|
||||
})
|
||||
|
||||
# select linked product
|
||||
option_ids = [l.id for l in so.order_line if l.linked_line_id.id == line.id]
|
||||
option_ids = [l for l in so.order_line if l.linked_line_id.id == line.id]
|
||||
|
||||
if option_ids:
|
||||
# update line
|
||||
sol.write(cr, SUPERUSER_ID, option_ids, {
|
||||
'product_uom_qty': value.get('quantity')
|
||||
}, context=context)
|
||||
# update line
|
||||
for l in option_ids:
|
||||
super(sale_order, self)._cart_update(cr, uid, ids, l.product_id.id, l.id, add_qty, set_qty, context=context, **kwargs)
|
||||
|
||||
value['option_ids'] = option_ids
|
||||
value['option_ids'] = [l.id for l in option_ids]
|
||||
return value
|
||||
|
||||
@@ -10,7 +10,7 @@ $(document).ready(function () {
|
||||
openerp.jsonRpc("/shop/modal", 'call', {
|
||||
'product_id': product_id,
|
||||
kwargs: {
|
||||
context: openerp.website.get_context()
|
||||
context: _.extend({'quantity': quantity}, openerp.website.get_context())
|
||||
},
|
||||
}).then(function (modal) {
|
||||
var $modal = $(modal);
|
||||
@@ -63,6 +63,34 @@ $(document).ready(function () {
|
||||
$('ul.js_add_cart_variants').each(function () {
|
||||
$('input.js_variant_change, select.js_variant_change', this).first().trigger('change');
|
||||
});
|
||||
|
||||
$modal.on("change", 'input[name="add_qty"]', function (event) {
|
||||
var product_id = $($modal.find('span.oe_price[data-oe-model="product.product"]').first()).data('oe-id');
|
||||
var default_price = parseInt($('.text-danger.oe_default_price > span.oe_currency_value').text());
|
||||
var $dom = $(event.target).closest('tr');
|
||||
var qty = $dom.find('input[name="add_qty"]').val();
|
||||
var product_ids = [product_id];
|
||||
var $products_dom = [];
|
||||
$modal.find("ul.js_add_cart_variants[data-attribute_value_ids]").each(function(){
|
||||
var $el = $(this);
|
||||
$products_dom.push($el);
|
||||
_.each($el.data("attribute_value_ids"), function (values) {
|
||||
product_ids.push(values[0]);
|
||||
});
|
||||
});
|
||||
openerp.jsonRpc("/shop/get_unit_price", 'call', {'product_ids': product_ids, 'add_qty': parseInt(qty)})
|
||||
.then(function (data) {
|
||||
for(var i=0; i < $products_dom.length; i++) {
|
||||
current = $products_dom[i].data("attribute_value_ids");
|
||||
for(var j=0; j < current.length; j++){
|
||||
current[j][2] = data[current[j][0]];
|
||||
}
|
||||
$products_dom[i].attr("data-attribute_value_ids", JSON.stringify(current)).trigger("change");
|
||||
}
|
||||
$dom.find(".oe_price .oe_currency_value").text(data[product_id].toFixed(2));
|
||||
$dom.find('.text-danger.oe_default_price').toggle(data[product_id]<default_price && (default_price-data[product_id]>default_price/100)).css('text-decoration', 'line-through');
|
||||
});
|
||||
});
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -102,15 +102,13 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<t t-if="(compute_currency(product.lst_price) - product.price) > 0.1">
|
||||
<span class="text-danger" style="text-decoration: line-through; white-space: nowrap;"
|
||||
<span class="text-danger oe_optional" t-att-style="'' if (compute_currency(product.lst_price) - product.price) > 0.1 else 'display: none;'" style="text-decoration: line-through; white-space: nowrap;"
|
||||
t-field="product.lst_price"
|
||||
t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "website.currency_id",
|
||||
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
|
||||
}'/><br/>
|
||||
</t>
|
||||
<span class="oe_price" style="white-space: nowrap;"
|
||||
t-field="product.price"
|
||||
t-field-options='{
|
||||
|
||||
@@ -50,7 +50,7 @@ def navbarify(node, navbar=None):
|
||||
# reference
|
||||
# no bullet_list.list_item -> don't dropdownify
|
||||
if not list_item.children[1].children:
|
||||
return
|
||||
continue
|
||||
|
||||
list_item['classes'].append('dropdown')
|
||||
# list_item.compact_paragraph.reference
|
||||
|
||||
@@ -795,7 +795,7 @@ set through the mapping argument, the default value will be used.
|
||||
Update records
|
||||
--------------
|
||||
|
||||
Reccords can be updated using :meth:`~openerp.models.Model.write`, it takes
|
||||
Records can be updated using :meth:`~openerp.models.Model.write`, it takes
|
||||
a list of records to update and a mapping of updated fields to values similar
|
||||
to :meth:`~openerp.models.Model.create`.
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
==================
|
||||
Business Mementoes
|
||||
==================
|
||||
|
||||
.. toctree::
|
||||
|
||||
Accounting Memento <https://odoo.com/documentation/functional/accounting.html>
|
||||
@@ -0,0 +1,15 @@
|
||||
Lima, Peru, 2015-04-01
|
||||
|
||||
Cubic ERP S.A.C. agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Cubic ERP S.A.C. info@cubicerp.com https://github.com/CubicERP
|
||||
|
||||
List of contributors:
|
||||
|
||||
Yury Oscar Tello Canchapoma ytello@cubicerp.com https://github.com/yurytello
|
||||
Virginia De la Cruz Sinchez vdelacruz@cubicerp.com https://github.com/virginiamaura
|
||||
Lisbeth Gutierrez Masa lgutierrez@cubicerp.com https://github.com/lisbethgutierrez
|
||||
@@ -0,0 +1,11 @@
|
||||
Spain, 2015-04-04
|
||||
|
||||
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Pedro M. Baeza pedro.baeza@gmail.com https://github.com/pedrobaeza
|
||||
@@ -194,7 +194,7 @@ def setup(app):
|
||||
app.add_config_value('versions', '', 'env')
|
||||
|
||||
app.connect('html-page-context', analytics)
|
||||
app.add_config_value('google_analytics_key', False, 'env')
|
||||
app.add_config_value('google_analytics_key', '', 'env')
|
||||
|
||||
def canonicalize(app, pagename, templatename, context, doctree):
|
||||
""" Adds a 'canonical' URL for the current document in the rendering
|
||||
|
||||
@@ -14,6 +14,7 @@ Odoo Technical Documentation
|
||||
setup
|
||||
reference
|
||||
api_integration
|
||||
business
|
||||
|
||||
.. ifconfig:: todo_include_todos
|
||||
|
||||
|
||||
@@ -335,10 +335,11 @@ class ir_attachment(osv.osv):
|
||||
if ids:
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
cr.execute('SELECT DISTINCT res_model, res_id FROM ir_attachment WHERE id = ANY (%s)', (ids,))
|
||||
for rmod, rid in cr.fetchall():
|
||||
cr.execute('SELECT DISTINCT res_model, res_id, create_uid FROM ir_attachment WHERE id = ANY (%s)', (ids,))
|
||||
for rmod, rid, create_uid in cr.fetchall():
|
||||
if not (rmod and rid):
|
||||
require_employee = True
|
||||
if create_uid != uid:
|
||||
require_employee = True
|
||||
continue
|
||||
res_ids.setdefault(rmod,set()).add(rid)
|
||||
if values:
|
||||
|
||||
@@ -102,10 +102,18 @@ class ir_http(osv.AbstractModel):
|
||||
|
||||
def _serve_attachment(self):
|
||||
domain = [('type', '=', 'binary'), ('url', '=', request.httprequest.path)]
|
||||
attach = self.pool['ir.attachment'].search_read(request.cr, openerp.SUPERUSER_ID, domain, ['__last_update', 'datas', 'mimetype', 'checksum'], context=request.context)
|
||||
attach = self.pool['ir.attachment'].search_read(request.cr, openerp.SUPERUSER_ID, domain, ['__last_update', 'datas', 'name', 'mimetype', 'checksum'], context=request.context)
|
||||
if attach:
|
||||
wdate = attach[0]['__last_update']
|
||||
datas = attach[0]['datas'] or '' # support empty ir_attachment
|
||||
datas = attach[0]['datas'] or ''
|
||||
name = attach[0]['name']
|
||||
|
||||
if not datas:
|
||||
if name.startswith(('http://', 'https://', '/')):
|
||||
return werkzeug.utils.redirect(name, 301)
|
||||
else:
|
||||
return werkzeug.wrappers.Response(status=204) # NO CONTENT
|
||||
|
||||
response = werkzeug.wrappers.Response()
|
||||
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
try:
|
||||
|
||||
@@ -377,10 +377,10 @@ class ir_mail_server(osv.osv):
|
||||
|
||||
'''
|
||||
get_param = self.pool['ir.config_parameter'].get_param
|
||||
postmaster = get_param(cr, uid, 'mail.bounce.alias',
|
||||
postmaster = get_param(cr, SUPERUSER_ID, 'mail.bounce.alias',
|
||||
default='postmaster-odoo',
|
||||
context=context,)
|
||||
domain = get_param(cr, uid, 'mail.catchall.domain', context=context)
|
||||
domain = get_param(cr, SUPERUSER_ID, 'mail.catchall.domain', context=context)
|
||||
if postmaster and domain:
|
||||
return '%s@%s' % (postmaster, domain)
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
MAX_CSS_RULES = 4095
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# QWeb template engine
|
||||
#--------------------------------------------------------------------
|
||||
@@ -1168,7 +1170,12 @@ class AssetsBundle(object):
|
||||
else:
|
||||
url_for = self.context.get('url_for', lambda url: url)
|
||||
if css and self.stylesheets:
|
||||
href = '/web/css/%s/%s' % (self.xmlid, self.version)
|
||||
suffix = ''
|
||||
if request:
|
||||
ua = request.httprequest.user_agent
|
||||
if ua.browser == "msie" and int((ua.version or '0').split('.')[0]) < 10:
|
||||
suffix = '.0'
|
||||
href = '/web/css%s/%s/%s' % (suffix, self.xmlid, self.version)
|
||||
response.append('<link href="%s" rel="stylesheet"/>' % url_for(href))
|
||||
if js:
|
||||
src = '/web/js/%s/%s' % (self.xmlid, self.version)
|
||||
@@ -1204,8 +1211,11 @@ class AssetsBundle(object):
|
||||
self.set_cache('js', content)
|
||||
return content
|
||||
|
||||
def css(self):
|
||||
def css(self, page_number=None):
|
||||
"""Generate css content from given bundle"""
|
||||
if page_number is not None:
|
||||
return self.css_page(page_number)
|
||||
|
||||
content = self.get_cache('css')
|
||||
if content is None:
|
||||
content = self.preprocess_css()
|
||||
@@ -1224,12 +1234,43 @@ class AssetsBundle(object):
|
||||
|
||||
matches.append(content)
|
||||
content = u'\n'.join(matches)
|
||||
if self.css_errors:
|
||||
return content
|
||||
self.set_cache('css', content)
|
||||
if not self.css_errors:
|
||||
self.set_cache('css', content)
|
||||
content = content.encode('utf-8')
|
||||
|
||||
return content
|
||||
|
||||
def css_page(self, page_number):
|
||||
content = self.get_cache('css.%d' % (page_number,))
|
||||
if page_number:
|
||||
return content
|
||||
if content is None:
|
||||
css = self.css().decode('utf-8')
|
||||
re_rules = '([^{]+\{(?:[^{}]|\{[^{}]*\})*\})'
|
||||
re_selectors = '()(?:\s*@media\s*[^{]*\{)?(?:\s*(?:[^,{]*(?:,|\{(?:[^}]*\}))))'
|
||||
css_url = '@import url(\'/web/css.%%d/%s/%s\');' % (self.xmlid, self.version)
|
||||
pages = [[]]
|
||||
page = pages[0]
|
||||
page_selectors = 0
|
||||
for rule in re.findall(re_rules, css):
|
||||
selectors = len(re.findall(re_selectors, rule))
|
||||
if page_selectors + selectors < MAX_CSS_RULES:
|
||||
page_selectors += selectors
|
||||
page.append(rule)
|
||||
else:
|
||||
pages.append([rule])
|
||||
page = pages[-1]
|
||||
page_selectors = selectors
|
||||
if len(pages) == 1:
|
||||
pages = []
|
||||
for idx, page in enumerate(pages):
|
||||
self.set_cache("css.%d" % (idx+1), ''.join(page))
|
||||
content = '\n'.join(css_url % i for i in range(1,len(pages)+1))
|
||||
self.set_cache("css.0", content)
|
||||
if not content:
|
||||
return self.css()
|
||||
return content
|
||||
|
||||
def get_cache(self, type):
|
||||
content = None
|
||||
domain = [('url', '=', '/web/%s/%s/%s' % (type, self.xmlid, self.version))]
|
||||
|
||||
@@ -119,23 +119,26 @@ class ir_translation_import_cursor(object):
|
||||
|
||||
# Records w/o res_id must _not_ be inserted into our db, because they are
|
||||
# referencing non-existent data.
|
||||
cr.execute("DELETE FROM %s WHERE res_id IS NULL AND module IS NOT NULL" % \
|
||||
self._table_name)
|
||||
cr.execute("DELETE FROM %s WHERE res_id IS NULL AND module IS NOT NULL" % self._table_name)
|
||||
|
||||
find_expr = "irt.lang = ti.lang AND irt.type = ti.type " \
|
||||
" AND irt.name = ti.name AND irt.src = ti.src " \
|
||||
" AND irt.module = ti.module " \
|
||||
" AND ( " \
|
||||
" (ti.type NOT IN ('model', 'view')) " \
|
||||
" OR (ti.type = 'model' AND ti.res_id = irt.res_id) " \
|
||||
" OR (ti.type = 'view' AND irt.res_id IS NULL) " \
|
||||
" OR (ti.type = 'view' AND irt.res_id IS NOT NULL AND ti.res_id = irt.res_id)) "
|
||||
find_expr = """
|
||||
irt.lang = ti.lang
|
||||
AND irt.type = ti.type
|
||||
AND irt.module = ti.module
|
||||
AND irt.name = ti.name
|
||||
AND (ti.type IN ('field', 'help') OR irt.src = ti.src)
|
||||
AND ( ti.type NOT IN ('model', 'view')
|
||||
OR (ti.type = 'model' AND ti.res_id = irt.res_id)
|
||||
OR (ti.type = 'view' AND (irt.res_id IS NULL OR ti.res_id = irt.res_id))
|
||||
)
|
||||
"""
|
||||
|
||||
# Step 2: update existing (matching) translations
|
||||
if self._overwrite:
|
||||
cr.execute("""UPDATE ONLY %s AS irt
|
||||
SET value = ti.value,
|
||||
state = 'translated'
|
||||
src = ti.src,
|
||||
state = 'translated'
|
||||
FROM %s AS ti
|
||||
WHERE %s AND ti.value IS NOT NULL AND ti.value != ''
|
||||
""" % (self._parent_table, self._table_name, find_expr))
|
||||
|
||||
@@ -38,6 +38,9 @@ from openerp.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# Only users who can modify the user (incl. the user herself) see the real contents of these fields
|
||||
USER_PRIVATE_FIELDS = ['password']
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Basic res.groups and res.users
|
||||
#----------------------------------------------------------
|
||||
@@ -280,8 +283,10 @@ class res_users(osv.osv):
|
||||
|
||||
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
|
||||
def override_password(o):
|
||||
if 'password' in o and ('id' not in o or o['id'] != uid):
|
||||
o['password'] = '********'
|
||||
if ('id' not in o or o['id'] != uid):
|
||||
for f in USER_PRIVATE_FIELDS:
|
||||
if f in o:
|
||||
o[f] = '********'
|
||||
return o
|
||||
|
||||
if fields and (ids == [uid] or ids == uid):
|
||||
|
||||
@@ -1807,7 +1807,7 @@ class BaseModel(object):
|
||||
``tools.ormcache`` or ``tools.ormcache_multi``.
|
||||
"""
|
||||
try:
|
||||
self.pool.cache.clear_prefix((self.pool.db_name, self._name))
|
||||
self.pool.cache.clear_prefix((self._name,))
|
||||
self.pool._any_cache_cleared = True
|
||||
except AttributeError:
|
||||
pass
|
||||
@@ -2421,8 +2421,12 @@ class BaseModel(object):
|
||||
"""
|
||||
self._foreign_keys = set()
|
||||
raise_on_invalid_object_name(self._name)
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
# This prevents anything called by this method (in particular default
|
||||
# values) from prefetching a field for which the corresponding column
|
||||
# has not been added in database yet!
|
||||
context = dict(context or {}, prefetch_fields=False)
|
||||
|
||||
store_compute = False
|
||||
stored_fields = [] # new-style stored fields with compute
|
||||
todo_end = []
|
||||
|
||||
@@ -73,6 +73,7 @@ class Registry(Mapping):
|
||||
self.base_registry_signaling_sequence = None
|
||||
self.base_cache_signaling_sequence = None
|
||||
|
||||
self.cache = LRU(8192)
|
||||
# Flag indicating if at least one model cache has been cleared.
|
||||
# Useful only in a multi-process context.
|
||||
self._any_cache_cleared = False
|
||||
@@ -104,10 +105,6 @@ class Registry(Mapping):
|
||||
""" Same as ``self[model_name]``. """
|
||||
return self.models[model_name]
|
||||
|
||||
@lazy_property
|
||||
def cache(self):
|
||||
return RegistryManager.cache
|
||||
|
||||
@lazy_property
|
||||
def pure_function_fields(self):
|
||||
""" Return the list of pure function fields (field objects) """
|
||||
@@ -292,7 +289,6 @@ class RegistryManager(object):
|
||||
|
||||
"""
|
||||
_registries = None
|
||||
_cache = None
|
||||
_lock = threading.RLock()
|
||||
_saved_lock = None
|
||||
|
||||
@@ -314,18 +310,6 @@ class RegistryManager(object):
|
||||
cls._registries = LRU(size)
|
||||
return cls._registries
|
||||
|
||||
@classproperty
|
||||
def cache(cls):
|
||||
""" Return the global LRU ormcache. Its keys are tuples with the
|
||||
following structure: (db_name, model_name, method, args...).
|
||||
"""
|
||||
with cls.lock():
|
||||
if cls._cache is None:
|
||||
# we allocate 8192 cache entries per registry
|
||||
size = 8192 * cls.registries.count
|
||||
cls._cache = LRU(size)
|
||||
return cls._cache
|
||||
|
||||
@classmethod
|
||||
def lock(cls):
|
||||
""" Return the current registry lock. """
|
||||
|
||||
@@ -155,8 +155,8 @@ def exp_dump(db_name, format):
|
||||
|
||||
def dump_db_manifest(cr):
|
||||
pg_version = "%d.%d" % divmod(cr._obj.connection.server_version / 100, 100)
|
||||
env = openerp.api.Environment(cr, SUPERUSER_ID, {})
|
||||
modules = dict([(i.name,i.latest_version) for i in env['ir.module.module'].search([('state','=','installed')])])
|
||||
cr.execute("SELECT name, latest_version FROM ir_module_module WHERE state = 'installed'")
|
||||
modules = dict(cr.fetchall())
|
||||
manifest = {
|
||||
'odoo_dump': '1',
|
||||
'db_name': cr.dbname,
|
||||
@@ -185,14 +185,13 @@ def dump_db(db_name, stream, backup_format='zip'):
|
||||
|
||||
if backup_format == 'zip':
|
||||
with openerp.tools.osutil.tempdir() as dump_dir:
|
||||
registry = openerp.modules.registry.RegistryManager.get(db_name)
|
||||
with registry.cursor() as cr:
|
||||
filestore = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
|
||||
if os.path.exists(filestore):
|
||||
shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
|
||||
manifest = dump_db_manifest(cr)
|
||||
with open(os.path.join(dump_dir, 'manifest.json'), 'w') as fh:
|
||||
json.dump(manifest, fh, indent=4)
|
||||
filestore = openerp.tools.config.filestore(db_name)
|
||||
if os.path.exists(filestore):
|
||||
shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
|
||||
with open(os.path.join(dump_dir, 'manifest.json'), 'w') as fh:
|
||||
db = openerp.sql_db.db_connect(db_name)
|
||||
with db.cursor() as cr:
|
||||
json.dump(dump_db_manifest(cr), fh, indent=4)
|
||||
cmd.insert(-1, '--file=' + os.path.join(dump_dir, 'dump.sql'))
|
||||
openerp.tools.exec_pg_command(*cmd)
|
||||
if stream:
|
||||
|
||||
@@ -59,7 +59,7 @@ class ormcache(object):
|
||||
return lookup
|
||||
|
||||
def lru(self, model):
|
||||
return model.pool.cache, (model.pool.db_name, model._name, self.method)
|
||||
return model.pool.cache, (model._name, self.method)
|
||||
|
||||
def lookup(self, method, *args, **kwargs):
|
||||
d, key0 = self.lru(args[0])
|
||||
@@ -181,16 +181,19 @@ def log_ormcache_stats(sig=None, frame=None):
|
||||
import threading
|
||||
|
||||
me = threading.currentThread()
|
||||
me_dbname = me.dbname
|
||||
entries = defaultdict(int)
|
||||
for key in RegistryManager.cache.iterkeys():
|
||||
entries[key[:3]] += 1
|
||||
for reg in RegistryManager.registries.itervalues():
|
||||
for key in reg.cache.iterkeys():
|
||||
entries[key[:3]] += 1
|
||||
for key, count in sorted(entries.items()):
|
||||
dbname, model_name, method = key
|
||||
me.dbname = dbname
|
||||
stat = STAT[key]
|
||||
_logger.info("%6d entries, %6d hit, %6d miss, %6d err, %4.1f%% ratio, for %s.%s",
|
||||
count, stat.hit, stat.miss, stat.err, stat.ratio, model_name, method.__name__)
|
||||
count, stat.hit, stat.miss, stat.err, stat.ratio, model_name, method.__name__)
|
||||
|
||||
me.dbname = me_dbname
|
||||
|
||||
# For backward compatibility
|
||||
cache = ormcache
|
||||
|
||||