[MERGE] forward port branch 11.0 up to 468dab6748
This commit is contained in:
@@ -42,7 +42,10 @@ class ReportAgedPartnerBalance(models.AbstractModel):
|
||||
res = []
|
||||
total = []
|
||||
cr = self.env.cr
|
||||
company_ids = self.env.context.get('company_ids', (self.env.user.company_id.id,))
|
||||
user_company = self.env.user.company_id
|
||||
user_currency = user_company.currency_id
|
||||
ResCurrency = self.env['res.currency'].with_context(date=date_from)
|
||||
company_ids = self._context.get('company_ids') or [user_company.id]
|
||||
move_state = ['draft', 'posted']
|
||||
if target_move == 'posted':
|
||||
move_state = ['posted']
|
||||
@@ -99,15 +102,15 @@ class ReportAgedPartnerBalance(models.AbstractModel):
|
||||
partner_id = line.partner_id.id or False
|
||||
if partner_id not in undue_amounts:
|
||||
undue_amounts[partner_id] = 0.0
|
||||
line_amount = line.balance
|
||||
if line.balance == 0:
|
||||
line_amount = ResCurrency._compute(line.company_id.currency_id, user_currency, line.balance)
|
||||
if user_currency.is_zero(line_amount):
|
||||
continue
|
||||
for partial_line in line.matched_debit_ids:
|
||||
if partial_line.max_date <= date_from:
|
||||
line_amount += partial_line.amount
|
||||
line_amount += ResCurrency._compute(partial_line.company_id.currency_id, user_currency, partial_line.amount)
|
||||
for partial_line in line.matched_credit_ids:
|
||||
if partial_line.max_date <= date_from:
|
||||
line_amount -= partial_line.amount
|
||||
line_amount -= ResCurrency._compute(partial_line.company_id.currency_id, user_currency, partial_line.amount)
|
||||
if not self.env.user.company_id.currency_id.is_zero(line_amount):
|
||||
undue_amounts[partner_id] += line_amount
|
||||
lines[partner_id].append({
|
||||
@@ -151,15 +154,15 @@ class ReportAgedPartnerBalance(models.AbstractModel):
|
||||
partner_id = line.partner_id.id or False
|
||||
if partner_id not in partners_amount:
|
||||
partners_amount[partner_id] = 0.0
|
||||
line_amount = line.balance
|
||||
if line.balance == 0:
|
||||
line_amount = ResCurrency._compute(line.company_id.currency_id, user_currency, line.balance)
|
||||
if user_currency.is_zero(line_amount):
|
||||
continue
|
||||
for partial_line in line.matched_debit_ids:
|
||||
if partial_line.max_date <= date_from:
|
||||
line_amount += partial_line.amount
|
||||
line_amount += ResCurrency._compute(partial_line.company_id.currency_id, user_currency, partial_line.amount)
|
||||
for partial_line in line.matched_credit_ids:
|
||||
if partial_line.max_date <= date_from:
|
||||
line_amount -= partial_line.amount
|
||||
line_amount -= ResCurrency._compute(partial_line.company_id.currency_id, user_currency, partial_line.amount)
|
||||
|
||||
if not self.env.user.company_id.currency_id.is_zero(line_amount):
|
||||
partners_amount[partner_id] += line_amount
|
||||
|
||||
@@ -39,8 +39,6 @@ access_account_journal_invoice,account.journal invoice,model_account_journal,acc
|
||||
access_account_invoice_group_invoice,account.invoice group invoice,model_account_invoice,account.group_account_invoice,1,1,1,1
|
||||
access_res_currency_account_manager,res.currency account manager,base.model_res_currency,group_account_manager,1,1,1,1
|
||||
access_res_currency_rate_account_manager,res.currency.rate account manager,base.model_res_currency_rate,group_account_manager,1,1,1,1
|
||||
access_account_invoice_user,account.invoice user,model_account_invoice,base.group_user,1,0,0,0
|
||||
access_account_invoice_line_user,account.invoice.line user,model_account_invoice_line,base.group_user,1,0,0,0
|
||||
access_account_invoice_portal,account.invoice.portal,account.model_account_invoice,base.group_portal,1,0,0,0
|
||||
access_account_invoice_line_portal,account.invoice.line.portal,account.model_account_invoice_line,base.group_portal,1,0,0,0
|
||||
access_account_payment_term_partner_manager,account.payment.term partner manager,model_account_payment_term,base.group_user,1,0,0,0
|
||||
|
||||
|
@@ -1208,8 +1208,9 @@ var ManualModel = StatementModel.extend({
|
||||
});
|
||||
|
||||
var domainReconcile = [];
|
||||
if (context && context.company_ids) {
|
||||
domainReconcile.push(['company_id', 'in', context.company_ids]);
|
||||
var company_ids = context && context.company_ids || [session.company_id]
|
||||
if (company_ids) {
|
||||
domainReconcile.push(['company_id', 'in', company_ids]);
|
||||
}
|
||||
var def_reconcileModel = this._rpc({
|
||||
model: 'account.reconcile.model',
|
||||
|
||||
@@ -45,4 +45,4 @@ class AccountCommonReport(models.TransientModel):
|
||||
data['form'] = self.read(['date_from', 'date_to', 'journal_ids', 'target_move', 'company_id'])[0]
|
||||
used_context = self._build_contexts(data)
|
||||
data['form']['used_context'] = dict(used_context, lang=self.env.context.get('lang') or 'en_US')
|
||||
return self._print_report(data)
|
||||
return self.with_context(discard_logo_check=True)._print_report(data)
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
attrs="{'invisible': [('type','!=','sale')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
</div>
|
||||
<field name="account_asset_id" nolabel="1" attrs="{'invisible': [('type','=', False)]}"/>
|
||||
<field name="account_asset_id" nolabel="1" attrs="{'invisible': [('type','=', False)]}" domain="[('company_id', '=', company_id)]"/>
|
||||
<div>
|
||||
<label for="account_depreciation_id"
|
||||
attrs="{'invisible': [('type','!=','purchase')]}"
|
||||
@@ -42,7 +42,7 @@
|
||||
attrs="{'invisible': [('type','!=','sale')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
</div>
|
||||
<field name="account_depreciation_id" nolabel="1"/>
|
||||
<field name="account_depreciation_id" nolabel="1" domain="[('company_id', '=', company_id)]"/>
|
||||
<div>
|
||||
<label for="account_depreciation_expense_id"
|
||||
attrs="{'invisible': [('type','!=','purchase')]}"
|
||||
@@ -51,8 +51,8 @@
|
||||
attrs="{'invisible': [('type','!=','sale')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
</div>
|
||||
<field name="account_depreciation_expense_id" nolabel="1"/>
|
||||
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
|
||||
<field name="account_depreciation_expense_id" nolabel="1" domain="[('company_id', '=', company_id)]"/>
|
||||
<field name="account_analytic_id" domain="[('company_id', '=', company_id)]" groups="analytic.group_analytic_accounting"/>
|
||||
</group>
|
||||
<group string="Periodicity">
|
||||
<field name="method_time" string="Time Method Based On" widget="radio" attrs="{'invisible': [('type','!=','purchase')]}"/>
|
||||
|
||||
@@ -58,17 +58,17 @@ class ResPartner(models.Model):
|
||||
for partner in self:
|
||||
# when required, make sure the partner has a valid signup token
|
||||
if self.env.context.get('signup_valid') and not partner.user_ids:
|
||||
partner.signup_prepare()
|
||||
partner.sudo().signup_prepare()
|
||||
|
||||
route = 'login'
|
||||
# the parameters to encode for the query
|
||||
query = dict(db=self.env.cr.dbname)
|
||||
signup_type = self.env.context.get('signup_force_type_in_url', partner.signup_type or '')
|
||||
signup_type = self.env.context.get('signup_force_type_in_url', partner.sudo().signup_type or '')
|
||||
if signup_type:
|
||||
route = 'reset_password' if signup_type == 'reset' else signup_type
|
||||
|
||||
if partner.signup_token and signup_type:
|
||||
query['token'] = partner.signup_token
|
||||
if partner.sudo().signup_token and signup_type:
|
||||
query['token'] = partner.sudo().signup_token
|
||||
elif partner.user_ids:
|
||||
query['login'] = partner.user_ids[0].login
|
||||
else:
|
||||
|
||||
@@ -25,18 +25,58 @@ class Partner(models.Model):
|
||||
# render the partner address accordingly to address_view_id
|
||||
doc = etree.fromstring(arch)
|
||||
if doc.xpath("//field[@name='city_id']"):
|
||||
return arch
|
||||
label = _('City')
|
||||
for city_node in doc.xpath("//field[@name='city']"):
|
||||
replacement_xml = """
|
||||
return arch
|
||||
|
||||
replacement_xml = """
|
||||
<div>
|
||||
<field name="country_enforce_cities" invisible="1"/>
|
||||
<field name='city' placeholder="%s" attrs="{'invisible': [('country_enforce_cities', '=', True), ('city_id', '!=', False)], 'readonly': [('type', '=', 'contact'), ('parent_id', '!=', False)]}"/>
|
||||
<field name='city_id' placeholder="%s" string="%s" attrs="{'invisible': [('country_enforce_cities', '=', False)], 'readonly': [('type', '=', 'contact'), ('parent_id', '!=', False)]}" context="{'default_country_id': country_id}" domain="[('country_id', '=', country_id)]"/>
|
||||
<field name='city' placeholder="%(placeholder)s"
|
||||
attrs="{
|
||||
'invisible': [('country_enforce_cities', '=', True), ('city_id', '!=', False)],
|
||||
'readonly': [('type', '=', 'contact')%(parent_condition)s]
|
||||
}"
|
||||
/>
|
||||
<field name='city_id' placeholder="%(placeholder)s" string="%(placeholder)s"
|
||||
context="{'default_country_id': country_id}"
|
||||
domain="[('country_id', '=', country_id)]"
|
||||
attrs="{
|
||||
'invisible': [('country_enforce_cities', '=', False)],
|
||||
'readonly': [('type', '=', 'contact')%(parent_condition)s]
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
""" % (label, label, label)
|
||||
city_id_node = etree.fromstring(replacement_xml)
|
||||
city_node.getparent().replace(city_node, city_id_node)
|
||||
"""
|
||||
|
||||
replacement_data = {
|
||||
'placeholder': _('City'),
|
||||
}
|
||||
|
||||
def _arch_location(node):
|
||||
in_subview = False
|
||||
view_type = False
|
||||
parent = node.getparent()
|
||||
while parent is not None and (not view_type or not in_subview):
|
||||
if parent.tag == 'field':
|
||||
in_subview = True
|
||||
elif parent.tag in ['list', 'tree', 'kanban', 'form']:
|
||||
view_type = parent.tag
|
||||
parent = parent.getparent()
|
||||
return {
|
||||
'view_type': view_type,
|
||||
'in_subview': in_subview,
|
||||
}
|
||||
|
||||
for city_node in doc.xpath("//field[@name='city']"):
|
||||
location = _arch_location(city_node)
|
||||
replacement_data['parent_condition'] = ''
|
||||
if location['view_type'] == 'form' or not location['in_subview']:
|
||||
replacement_data['parent_condition'] = ", ('parent_id', '!=', False)"
|
||||
|
||||
replacement_formatted = replacement_xml % replacement_data
|
||||
for replace_node in etree.fromstring(replacement_formatted).getchildren():
|
||||
city_node.addprevious(replace_node)
|
||||
parent = city_node.getparent()
|
||||
parent.remove(city_node)
|
||||
|
||||
arch = etree.tostring(doc, encoding='unicode')
|
||||
return arch
|
||||
|
||||
@@ -1757,7 +1757,15 @@ class Meeting(models.Model):
|
||||
if values.get('description'):
|
||||
activity_values['note'] = values['description']
|
||||
if values.get('start'):
|
||||
activity_values['date_deadline'] = fields.Datetime.from_string(values['start']).date()
|
||||
# self.start is a datetime UTC *only when the event is not allday*
|
||||
# activty.date_deadline is a date (No TZ, but should represent the day in which the user's TZ is)
|
||||
# See 72254129dbaeae58d0a2055cba4e4a82cde495b7 for the same issue, but elsewhere
|
||||
deadline = fields.Datetime.from_string(values['start'])
|
||||
user_tz = self.env.context.get('tz')
|
||||
if user_tz and not self.allday:
|
||||
deadline = pytz.UTC.localize(deadline)
|
||||
deadline = deadline.astimezone(pytz.timezone(user_tz))
|
||||
activity_values['date_deadline'] = deadline.date()
|
||||
if values.get('user_id'):
|
||||
activity_values['user_id'] = values['user_id']
|
||||
if activity_values.keys():
|
||||
|
||||
@@ -314,3 +314,73 @@ class TestCalendar(TransactionCase):
|
||||
else:
|
||||
self.assertEqual(d.hour, 15)
|
||||
self.assertEqual(d.minute, 30)
|
||||
|
||||
def test_event_activity_timezone(self):
|
||||
activty_type = self.env['mail.activity.type'].create({
|
||||
'name': 'Meeting',
|
||||
'category': 'meeting'
|
||||
})
|
||||
|
||||
activity_id = self.env['mail.activity'].create({
|
||||
'summary': 'Meeting with partner',
|
||||
'activity_type_id': activty_type.id,
|
||||
'res_model_id': self.env['ir.model'].search([('model', '=', 'res.partner')], limit=1).id,
|
||||
'res_id': self.env['res.partner'].search([('name', 'ilike', 'Agrolait')], limit=1).id,
|
||||
})
|
||||
|
||||
calendar_event = self.env['calendar.event'].create({
|
||||
'name': 'Meeting with partner',
|
||||
'activity_ids': [(6, False, activity_id.ids)],
|
||||
'start': '2018-11-12 21:00:00',
|
||||
'stop': '2018-11-13 00:00:00',
|
||||
})
|
||||
|
||||
# Check output in UTC
|
||||
self.assertEqual(activity_id.date_deadline, '2018-11-12')
|
||||
|
||||
# Check output in the user's tz
|
||||
# write on the event to trigger sync of activities
|
||||
calendar_event.with_context({'tz': 'Australia/Brisbane'}).write({
|
||||
'start': '2018-11-12 21:00:00',
|
||||
})
|
||||
|
||||
self.assertEqual(activity_id.date_deadline, '2018-11-13')
|
||||
|
||||
def test_event_allday_activity_timezone(self):
|
||||
# Covers use case of commit eef4c3b48bcb4feac028bf640b545006dd0c9b91
|
||||
# Also, read the comment in the code at calendar.event._inverse_dates
|
||||
activty_type = self.env['mail.activity.type'].create({
|
||||
'name': 'Meeting',
|
||||
'category': 'meeting'
|
||||
})
|
||||
|
||||
activity_id = self.env['mail.activity'].create({
|
||||
'summary': 'Meeting with partner',
|
||||
'activity_type_id': activty_type.id,
|
||||
'res_model_id': self.env['ir.model'].search([('model', '=', 'res.partner')], limit=1).id,
|
||||
'res_id': self.env['res.partner'].search([('name', 'ilike', 'Agrolait')], limit=1).id,
|
||||
})
|
||||
|
||||
calendar_event = self.env['calendar.event'].create({
|
||||
'name': 'All Day',
|
||||
'start': "2018-10-16 00:00:00",
|
||||
'start_date': "2018-10-16",
|
||||
'start_datetime': False,
|
||||
'stop': "2018-10-18 00:00:00",
|
||||
'stop_date': "2018-10-18",
|
||||
'stop_datetime': False,
|
||||
'allday': True,
|
||||
'activity_ids': [(6, False, activity_id.ids)],
|
||||
})
|
||||
|
||||
# Check output in UTC
|
||||
self.assertEqual(activity_id.date_deadline, '2018-10-16')
|
||||
|
||||
# Check output in the user's tz
|
||||
# write on the event to trigger sync of activities
|
||||
calendar_event.with_context({'tz': 'Pacific/Honolulu'}).write({
|
||||
'start': '2018-10-16 00:00:00',
|
||||
'start_date': '2018-10-16',
|
||||
})
|
||||
|
||||
self.assertEqual(activity_id.date_deadline, '2018-10-16')
|
||||
|
||||
@@ -217,7 +217,7 @@ class DeliveryCarrier(models.Model):
|
||||
'error_message': _('Error: this delivery method is not available for this address.'),
|
||||
'warning_message': False}
|
||||
price = self.fixed_price
|
||||
if self.company_id.currency_id.id != order.currency_id.id:
|
||||
if self.company_id and self.company_id.currency_id.id != order.currency_id.id:
|
||||
price = self.env['res.currency']._compute(self.company_id.currency_id, order.currency_id, price)
|
||||
return {'success': True,
|
||||
'price': price,
|
||||
|
||||
@@ -155,6 +155,18 @@ var Chatter = Widget.extend({
|
||||
this.composer.clear_composer();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_disableComposer: function () {
|
||||
this.$(".o_composer_button_send").prop('disabled', true);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_enableComposer: function () {
|
||||
this.$(".o_composer_button_send").prop('disabled', false);
|
||||
},
|
||||
/**
|
||||
* Discard changes on the record.
|
||||
*
|
||||
@@ -231,12 +243,15 @@ var Chatter = Widget.extend({
|
||||
self.composer.focus();
|
||||
}
|
||||
self.composer.on('post_message', self, function (message) {
|
||||
self._disableComposer();
|
||||
self._discardOnReload(message).then(function () {
|
||||
self.fields.thread.postMessage(message).then(function () {
|
||||
self._closeComposer(true);
|
||||
if (self._reloadAfterPost(message)) {
|
||||
self.trigger_up('reload');
|
||||
}
|
||||
}).fail(function () {
|
||||
self._enableComposer();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -486,13 +486,10 @@ var BasicComposer = Widget.extend({
|
||||
},
|
||||
|
||||
send_message: function () {
|
||||
var $button = this.$('.o_composer_button_send');
|
||||
|
||||
if (this.is_empty() || !this.do_check_attachment_upload()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$button.prop('disabled', true);
|
||||
clearTimeout(this.canned_timeout);
|
||||
var self = this;
|
||||
this.preprocess_message().then(function (message) {
|
||||
|
||||
@@ -436,6 +436,8 @@ td {
|
||||
display: flex;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
max-width: -moz-available;
|
||||
max-width: -webkit-fill-available;
|
||||
}
|
||||
.pos .orders {
|
||||
display: -webkit-flex;
|
||||
|
||||
@@ -139,8 +139,10 @@ models.Orderline = models.Orderline.extend({
|
||||
}
|
||||
},
|
||||
set_dirty: function(dirty) {
|
||||
this.mp_dirty = dirty;
|
||||
this.trigger('change',this);
|
||||
if (this.mp_dirty !== dirty) {
|
||||
this.mp_dirty = dirty;
|
||||
this.trigger('change', this);
|
||||
}
|
||||
},
|
||||
get_line_diff_hash: function(){
|
||||
if (this.get_note()) {
|
||||
|
||||
@@ -16,7 +16,7 @@ class report_product_pricelist(models.AbstractModel):
|
||||
quantities = self._get_quantity(data)
|
||||
return {
|
||||
'doc_ids': data.get('ids', data.get('active_ids')),
|
||||
'doc_model': 'hr.contribution.register',
|
||||
'doc_model': 'product.pricelist',
|
||||
'docs': products,
|
||||
'data': dict(
|
||||
data,
|
||||
|
||||
@@ -481,3 +481,15 @@ class StockMove(models.Model):
|
||||
return [(requisition_line.requisition_id, requisition_line.requisition_id.user_id, visited) for requisition_line in self.requisition_line_ids if requisition_line.state not in ('done', 'cancel')]
|
||||
else:
|
||||
return super(StockMove, self)._get_upstream_documents_and_responsibles(visited)
|
||||
|
||||
|
||||
class Orderpoint(models.Model):
|
||||
_inherit = "stock.warehouse.orderpoint"
|
||||
|
||||
def _quantity_in_progress(self):
|
||||
res = super(Orderpoint, self)._quantity_in_progress()
|
||||
for op in self:
|
||||
for pr in self.env['purchase.requisition'].search([('state','=','draft'),('origin','=',op.name)]):
|
||||
for prline in pr.line_ids.filtered(lambda l: l.product_id.id == op.product_id.id):
|
||||
res[op.id] += prline.product_uom_id._compute_quantity(prline.product_qty, op.product_uom, round=False)
|
||||
return res
|
||||
|
||||
@@ -469,10 +469,11 @@ class SaleOrder(models.Model):
|
||||
raise UserError(_('There is no invoiceable line.'))
|
||||
|
||||
for invoice in invoices.values():
|
||||
invoice.compute_taxes()
|
||||
if not invoice.invoice_line_ids:
|
||||
raise UserError(_('There is no invoiceable line.'))
|
||||
# If invoice is negative, do a refund invoice instead
|
||||
if invoice.amount_untaxed < 0:
|
||||
if invoice.amount_total < 0:
|
||||
invoice.type = 'out_refund'
|
||||
for line in invoice.invoice_line_ids:
|
||||
line.quantity = -line.quantity
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<field name="model">sale.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Sales Analysis">
|
||||
<field name="date" type="row"/>
|
||||
<field name="confirmation_date" type="row"/>
|
||||
<field name="price_subtotal" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
|
||||
@@ -60,10 +60,6 @@ class SaleOrderLine(models.Model):
|
||||
for line in self:
|
||||
currency = line.order_id.pricelist_id.currency_id
|
||||
price = line.purchase_price
|
||||
if not price:
|
||||
from_cur = line.env.user.company_id.currency_id.with_context(date=line.order_id.date_order)
|
||||
price = from_cur.compute(line.product_id.standard_price, currency, round=False)
|
||||
|
||||
line.margin = currency.round(line.price_subtotal - (price * line.product_uom_qty))
|
||||
|
||||
|
||||
|
||||
@@ -300,7 +300,10 @@ class SaleOrderLine(models.Model):
|
||||
self.ensure_one()
|
||||
qty = 0.0
|
||||
for move in self.move_ids.filtered(lambda r: r.state != 'cancel'):
|
||||
qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP')
|
||||
if move.picking_code == 'outgoing':
|
||||
qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP')
|
||||
elif move.picking_code == 'incoming':
|
||||
qty -= move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP')
|
||||
return qty
|
||||
|
||||
@api.multi
|
||||
@@ -406,7 +409,8 @@ class SaleOrderLine(models.Model):
|
||||
return is_available
|
||||
|
||||
def _update_line_quantity(self, values):
|
||||
if self.mapped('qty_delivered') and values['product_uom_qty'] < max(self.mapped('qty_delivered')):
|
||||
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||
if self.mapped('qty_delivered') and float_compare(values['product_uom_qty'], max(self.mapped('qty_delivered')), precision_digits=precision) == -1:
|
||||
raise UserError(_('You cannot decrease the ordered quantity below the delivered quantity.\n'
|
||||
'Create a return first.'))
|
||||
super(SaleOrderLine, self)._update_line_quantity(values)
|
||||
|
||||
@@ -398,7 +398,7 @@
|
||||
<field name="act_window_id" ref="stock_move_action"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window.view" id="action_stock_move_graph_all">
|
||||
<record model="ir.actions.act_window.view" id="action_stock_move_kanban_all">
|
||||
<field name="sequence" eval="4"/>
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="view_id" ref="view_move_kandan"/>
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Detailed Operations" attrs="{'invisible': [('show_operations', '=', False)]}">
|
||||
<field name="move_line_ids" attrs="{'readonly': ['|', '&', ('show_operations', '=', False), ('state', '=', 'done'), ('is_locked', '=', True)]}" context="{'default_picking_id': id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}">
|
||||
<field name="move_line_ids" attrs="{'readonly': ['|', ('show_operations', '=', False), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}" context="{'default_picking_id': id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}">
|
||||
<tree editable="bottom" decoration-muted="(state == 'done' and is_locked == True)" decoration-danger="qty_done>product_uom_qty and state!='done'" decoration-success="qty_done==product_uom_qty and state!='done' and not result_package_id">
|
||||
<field name="product_id" required="1" attrs="{'readonly': ['|', ('state', '=', 'done'), ('move_id', '!=', False)]}"/>
|
||||
<field name="move_id" invisible="1"/>
|
||||
|
||||
@@ -165,9 +165,12 @@ var FormViewDialog = ViewDialog.extend({
|
||||
}
|
||||
|
||||
fields_view_def.then(function (viewInfo) {
|
||||
var refinedContext = _.pick(self.context, function (value, key) {
|
||||
return key.indexOf('_view_ref') === -1;
|
||||
});
|
||||
var formview = new FormView(viewInfo, {
|
||||
modelName: self.res_model,
|
||||
context: self.context,
|
||||
context: refinedContext,
|
||||
ids: self.res_id ? [self.res_id] : [],
|
||||
currentId: self.res_id || undefined,
|
||||
index: 0,
|
||||
|
||||
@@ -364,6 +364,61 @@ QUnit.module('Views', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Form dialog and subview with _view_ref contexts', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data.instrument.records = [{id: 1, name: 'Tromblon', badassery: [1]}];
|
||||
this.data.partner.records[0].instrument = 1;
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="name"/>' +
|
||||
'<field name="instrument" context="{\'tree_view_ref\': \'some_tree_view\'}"/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
archs: {
|
||||
'instrument,false,form': '<form>'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="badassery" context="{\'tree_view_ref\': \'some_other_tree_view\'}"/>' +
|
||||
'</form>',
|
||||
|
||||
'badassery,false,list': '<tree>'+
|
||||
'<field name="level"/>'+
|
||||
'</tree>',
|
||||
},
|
||||
viewOptions: {
|
||||
mode: 'edit',
|
||||
},
|
||||
|
||||
mockRPC: function(route, args) {
|
||||
if (args.method === 'get_formview_id') {
|
||||
return $.when(false);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
|
||||
interceptsPropagate: {
|
||||
load_views: function (ev) {
|
||||
var evaluatedContext = ev.data.context.eval();
|
||||
if (ev.data.modelName === 'instrument') {
|
||||
assert.deepEqual(evaluatedContext, {tree_view_ref: 'some_tree_view'},
|
||||
'The correct _view_ref should have been sent to the server, first time');
|
||||
}
|
||||
if (ev.data.modelName === 'badassery') {
|
||||
assert.deepEqual(evaluatedContext, {tree_view_ref: 'some_other_tree_view'},
|
||||
'The correct _view_ref should have been sent to the server for the subview');
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
form.$('.o_field_widget[name="instrument"] button.o_external_button').click();
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('SelectCreateDialog: save current search', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
|
||||
@@ -1893,6 +1893,15 @@ $.summernote.pluginEvents.formatBlock = function (event, editor, layoutInfo, sTa
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
// select content since container (that firefox selects) may be removed
|
||||
if (r.so === 0) {
|
||||
r.sc = dom.firstChild(r.sc);
|
||||
}
|
||||
if (dom.nodeLength(r.ec) >= r.eo) {
|
||||
r.ec = dom.lastChild(r.ec);
|
||||
r.eo = dom.nodeLength(r.ec);
|
||||
}
|
||||
r = range.create(r.sc, r.so, r.ec, r.eo);
|
||||
r.reRange().select();
|
||||
|
||||
if (sTagName === "blockquote" || sTagName === "pre") {
|
||||
@@ -1906,6 +1915,10 @@ $.summernote.pluginEvents.formatBlock = function (event, editor, layoutInfo, sTa
|
||||
for (var i=0; i<nodes.length; i++) {
|
||||
if (dom.isBR(nodes[i]) || (dom.isText(nodes[i]) && dom.isVisibleText(nodes[i])) || dom.isB(nodes[i]) || dom.isU(nodes[i]) || dom.isS(nodes[i]) || dom.isI(nodes[i]) || dom.isFont(nodes[i])) {
|
||||
var ancestor = dom.ancestor(nodes[i], isFormatNode);
|
||||
if ($(ancestor).parent().is('blockquote')) {
|
||||
// firefox may wrap formatting block in blockquote
|
||||
$(ancestor).unwrap();
|
||||
}
|
||||
if (!ancestor) {
|
||||
dom.wrap(nodes[i], sTagName);
|
||||
} else if (ancestor.tagName.toLowerCase() !== sTagName) {
|
||||
@@ -2273,7 +2286,8 @@ options.onCreateLink = function (sLinkUrl) {
|
||||
// pass
|
||||
} else if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
|
||||
sLinkUrl = 'mailto:' + sLinkUrl;
|
||||
} else if (sLinkUrl.indexOf('://') === -1 && sLinkUrl.indexOf('/') !== 0 && sLinkUrl.indexOf('#') !== 0) {
|
||||
} else if (sLinkUrl.indexOf('://') === -1 && sLinkUrl[0] !== '/'
|
||||
&& sLinkUrl[0] !== '#' && sLinkUrl.slice(0, 2) !== '${') {
|
||||
sLinkUrl = 'http://' + sLinkUrl;
|
||||
}
|
||||
return sLinkUrl;
|
||||
|
||||
@@ -38,7 +38,7 @@ class ServerAction(models.Model):
|
||||
def _get_website_url(self):
|
||||
for action in self:
|
||||
if action.state == 'code' and action.website_published:
|
||||
action.website_url = self._compute_website_url(action.website_path, action.xml_id)
|
||||
action.website_url = action._compute_website_url(action.website_path, action.xml_id)
|
||||
|
||||
@api.model
|
||||
def _get_eval_context(self, action):
|
||||
|
||||
@@ -81,14 +81,14 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-xs-offset-3">
|
||||
<t t-call="website.pager"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 oe_structure" id="right_column">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-xs-offset-3">
|
||||
<t t-call="website.pager"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
</div>
|
||||
|
||||
@@ -221,10 +221,10 @@ class WebsiteForm(http.Controller):
|
||||
else:
|
||||
orphan_attachment_ids.append(attachment_id.id)
|
||||
|
||||
# If some attachments didn't match a field on the model,
|
||||
# we create a mail.message to link them to the record
|
||||
if orphan_attachment_ids:
|
||||
if model_name != 'mail.mail':
|
||||
if model_name != 'mail.mail':
|
||||
# If some attachments didn't match a field on the model,
|
||||
# we create a mail.message to link them to the record
|
||||
if orphan_attachment_ids:
|
||||
values = {
|
||||
'body': _('<p>Attached files : </p>'),
|
||||
'model': model_name,
|
||||
|
||||
@@ -8,7 +8,7 @@ declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Niyas Raphy niyas@cybrosys.com https://github.com/CybroOdoo
|
||||
Sainu Abideen sainu@cybrosys.com https://github.com/CybroOdoo
|
||||
|
||||
List of contributors:
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
Belgium, 2018-11-05
|
||||
|
||||
Okia SPRL 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,
|
||||
|
||||
Sylvain Van Hoof sylvain@okia.be https://github.com/sylvainvh
|
||||
|
||||
List of contributors:
|
||||
|
||||
Sylvain Van Hoof sylvain@okia.be https://github.com/sylvainvh
|
||||
@@ -0,0 +1,16 @@
|
||||
France, 2018-11-14
|
||||
|
||||
Subteno IT 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,
|
||||
|
||||
Sébastien LANGE sebastien.lange@subteno-it.fr https://github.com/sla-subteno-it
|
||||
|
||||
List of contributors:
|
||||
|
||||
Christian LECOUFLE christian.lecoufle@subteno-it.fr https://github.com/cle-subteno-it
|
||||
Vincent COFFIN vincent.coffin@subteno-it.fr https://github.com/vco-subteno-it
|
||||
@@ -0,0 +1,11 @@
|
||||
Spain, 2018-10-17
|
||||
|
||||
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,
|
||||
|
||||
Ugaitz Olaizola Arbelaitz uolaizola@binovo.es
|
||||
@@ -1,18 +0,0 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: OpenERP
|
||||
Version: 6.0.0
|
||||
Author: OpenERP S.A.
|
||||
Author-email: fp at openerp com
|
||||
Maintainer: OpenERP S.A.
|
||||
Maintainer-email: fp at openerp com
|
||||
Home-page: http://www.openerp.com
|
||||
Download-url: http://www.openerp.com/downloads.html
|
||||
Summary: OpenERP is an Enterprise Resource Management written entirely in python.
|
||||
License: GPL-3
|
||||
Description: OpenERP is a complete ERP and CRM. The main features are accounting (analytic
|
||||
and financial), stock management, sales and purchases management, tasks
|
||||
automation, marketing campaigns, help desk, POS, etc. Technical features include
|
||||
a distributed server, flexible workflows, an object database, a dynamic GUI,
|
||||
customizable reports, and an XML-RPC interface.
|
||||
Keywords: ERP, Accounting, Stock, CRM, Enterprise, Logistics, Management, Sales, Purchases
|
||||
Platform: Linux, Win32
|
||||
@@ -32,7 +32,7 @@
|
||||
<rng:ref name="field"/>
|
||||
</rng:zeroOrMore>
|
||||
<rng:optional>
|
||||
<rng:element name="template">
|
||||
<rng:element name="templates">
|
||||
<rng:oneOrMore>
|
||||
<rng:ref name="any"/>
|
||||
</rng:oneOrMore>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ lxml ; sys_platform == 'win32'
|
||||
Mako==1.0.4
|
||||
MarkupSafe==0.23
|
||||
mock==2.0.0
|
||||
num2words==0.5.4
|
||||
num2words==0.5.6
|
||||
ofxparse==0.16
|
||||
passlib==1.6.5
|
||||
Pillow==4.0.0
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import re
|
||||
from glob import glob
|
||||
from setuptools import find_packages, setup
|
||||
from os.path import join, dirname
|
||||
|
||||
@@ -11,114 +8,6 @@ from os.path import join, dirname
|
||||
exec(open(join(dirname(__file__), 'odoo', 'release.py'), 'rb').read()) # Load release variables
|
||||
lib_name = 'odoo'
|
||||
|
||||
|
||||
def py2exe_datafiles():
|
||||
data_files = {}
|
||||
data_files['Microsoft.VC90.CRT'] = glob('C:\Microsoft.VC90.CRT\*.*')
|
||||
|
||||
for root, dirnames, filenames in os.walk('odoo'):
|
||||
for filename in filenames:
|
||||
if not re.match(r'.*(\.pyc|\.pyo|\~)$', filename):
|
||||
data_files.setdefault(root, []).append(join(root, filename))
|
||||
|
||||
import babel
|
||||
data_files['babel/localedata'] = glob(join(dirname(babel.__file__), 'localedata', '*'))
|
||||
others = ['global.dat', 'numbers.py', 'support.py', 'plural.py']
|
||||
data_files['babel'] = [join(dirname(babel.__file__), f) for f in others]
|
||||
others = ['frontend.py', 'mofile.py']
|
||||
data_files['babel/messages'] = [join(dirname(babel.__file__), 'messages', f) for f in others]
|
||||
|
||||
import pytz
|
||||
tzdir = dirname(pytz.__file__)
|
||||
for root, _, filenames in os.walk(join(tzdir, 'zoneinfo')):
|
||||
base = join('pytz', root[len(tzdir) + 1:])
|
||||
data_files[base] = [join(root, f) for f in filenames]
|
||||
|
||||
import docutils
|
||||
import passlib
|
||||
import reportlab
|
||||
import requests
|
||||
data_mapping = ((docutils, 'docutils'),
|
||||
(passlib, 'passlib'),
|
||||
(reportlab, 'reportlab'),
|
||||
(requests, 'requests'))
|
||||
|
||||
for mod, datadir in data_mapping:
|
||||
basedir = dirname(mod.__file__)
|
||||
for root, _, filenames in os.walk(basedir):
|
||||
base = join(datadir, root[len(basedir) + 1:])
|
||||
data_files[base] = [join(root, f)
|
||||
for f in filenames
|
||||
if not f.endswith(('.py', '.pyc', '.pyo'))]
|
||||
|
||||
return list(data_files.items())
|
||||
|
||||
|
||||
def py2exe_options():
|
||||
if os.name == 'nt':
|
||||
import py2exe
|
||||
return {
|
||||
'console': [
|
||||
{'script': 'odoo-bin', 'icon_resources': [
|
||||
(1, join('setup', 'win32', 'static', 'pixmaps', 'openerp-icon.ico'))
|
||||
]},
|
||||
],
|
||||
'options': {
|
||||
'py2exe': {
|
||||
'skip_archive': 1,
|
||||
'optimize': 0, # Keep the assert running as the integrated tests rely on them.
|
||||
'dist_dir': 'dist',
|
||||
'packages': [
|
||||
'asynchat', 'asyncore',
|
||||
'BeautifulSoup',
|
||||
'commands',
|
||||
'dateutil',
|
||||
'decimal',
|
||||
'decorator',
|
||||
'docutils',
|
||||
'email',
|
||||
'encodings',
|
||||
'HTMLParser',
|
||||
'imaplib',
|
||||
'jinja2',
|
||||
'lxml', 'lxml._elementpath', 'lxml.builder', 'lxml.etree', 'lxml.objectify',
|
||||
'mako',
|
||||
'markupsafe',
|
||||
'mock',
|
||||
'ofxparse',
|
||||
'odoo',
|
||||
'passlib',
|
||||
'PIL',
|
||||
'poplib',
|
||||
'psutil',
|
||||
'pychart',
|
||||
'pydot',
|
||||
'pyparsing',
|
||||
'PyPDF2',
|
||||
'pytz',
|
||||
'reportlab',
|
||||
'requests',
|
||||
'select',
|
||||
'smtplib',
|
||||
'suds',
|
||||
'uuid',
|
||||
'vatnumber',
|
||||
'vobject',
|
||||
'win32service', 'win32serviceutil',
|
||||
'xlrd',
|
||||
'xlsxwriter',
|
||||
'xlwt',
|
||||
'xml', 'xml.dom',
|
||||
],
|
||||
'excludes': ['Tkconstants', 'Tkinter', 'tcl'],
|
||||
}
|
||||
},
|
||||
'data_files': py2exe_datafiles()
|
||||
}
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
setup(
|
||||
name='odoo',
|
||||
version=version,
|
||||
@@ -174,5 +63,4 @@ setup(
|
||||
tests_require=[
|
||||
'mock',
|
||||
],
|
||||
**py2exe_options()
|
||||
)
|
||||
|
||||
@@ -5,9 +5,6 @@ include Makefile.servicename
|
||||
SERVER_DIRECTORY=../..
|
||||
FILES_DIRECTORY=release
|
||||
|
||||
LAUNCH_PY2EXE_SERVICE=/cygdrive/c/python${PYTHON_VERSION}/python.exe win32_setup.py py2exe
|
||||
LAUNCH_PY2EXE=/cygdrive/c/python${PYTHON_VERSION}/python.exe setup.py py2exe
|
||||
|
||||
MAKENSIS_ARGUMENTS=/DVERSION=$(VERSION) /DSERVICENAME=${SERVICENAME}
|
||||
|
||||
LAUNCH_MAKENSIS=/cygdrive/c/tools/cygwin/makensis $(MAKENSIS_ARGUMENTS) setup.nsi
|
||||
@@ -27,8 +24,6 @@ server_clean:
|
||||
rm -rf $(SERVER_DIRECTORY)/.cyg*
|
||||
|
||||
allinone: server_clean
|
||||
#(cd $(SERVER_DIRECTORY)/setup/win32 && $(LAUNCH_PY2EXE_SERVICE))
|
||||
#(cd $(SERVER_DIRECTORY) && $(LAUNCH_PY2EXE))
|
||||
(cd $(SERVER_DIRECTORY)/setup/win32 && $(LAUNCH_MAKENSIS))
|
||||
(cd $(SERVER_DIRECTORY)/setup/win32 && mkdir -p $(FILES_DIRECTORY))
|
||||
(cd $(SERVER_DIRECTORY)/setup/win32 && cp openerp-*.exe $(FILES_DIRECTORY)/openerp-server-setup-$(VERSION).exe)
|
||||
|
||||
Reference in New Issue
Block a user