import time
from openerp.osv import fields, osv
+from openerp.tools import float_compare
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
if (statement.journal_id.type not in ('cash',)):
continue
if not statement.journal_id.cash_control:
- if statement.balance_end_real <> statement.balance_end:
+ prec = self.pool['decimal.precision'].precision_get(cr, uid, 'Account')
+ if float_compare(statement.balance_end_real, statement.balance_end, precision_digits=prec):
statement.write({'balance_end_real' : statement.balance_end})
continue
start = end = 0
})
# update taxes
ir_values = self.pool.get('ir.values')
- taxes_id = ir_values.get_default(cr, uid, 'product.product', 'taxes_id', company_id=company_id)
- supplier_taxes_id = ir_values.get_default(cr, uid, 'product.product', 'supplier_taxes_id', company_id=company_id)
+ taxes_id = ir_values.get_default(cr, uid, 'product.template', 'taxes_id', company_id=company_id)
+ supplier_taxes_id = ir_values.get_default(cr, uid, 'product.template', 'supplier_taxes_id', company_id=company_id)
values.update({
'default_sale_tax': isinstance(taxes_id, list) and taxes_id[0] or taxes_id,
'default_purchase_tax': isinstance(supplier_taxes_id, list) and supplier_taxes_id[0] or supplier_taxes_id,
raise openerp.exceptions.AccessError(_("Only administrators can change the settings"))
ir_values = self.pool.get('ir.values')
config = self.browse(cr, uid, ids[0], context)
- ir_values.set_default(cr, SUPERUSER_ID, 'product.product', 'taxes_id',
+ ir_values.set_default(cr, SUPERUSER_ID, 'product.template', 'taxes_id',
config.default_sale_tax and [config.default_sale_tax.id] or False, company_id=config.company_id.id)
- ir_values.set_default(cr, SUPERUSER_ID, 'product.product', 'supplier_taxes_id',
+ ir_values.set_default(cr, SUPERUSER_ID, 'product.template', 'supplier_taxes_id',
config.default_purchase_tax and [config.default_purchase_tax.id] or False, company_id=config.company_id.id)
def set_chart_of_accounts(self, cr, uid, ids, context=None):
this.max_move_lines_displayed = 5;
this.animation_speed = 100; // "Blocking" animations
this.aestetic_animation_speed = 300; // eye candy
+ this.map_currency_id_rounding = {};
this.map_tax_id_amount = {};
this.presets = {};
// We'll need to get the code of an account selected in a many2one (whose value is the id)
_.each(data, function(o) { self.map_account_id_code[o.id] = o.code });
});
+ // Create a dict currency id -> rounding factor
+ new instance.web.Model("res.currency")
+ .query(['id', 'rounding'])
+ .all().then(function(data) {
+ _.each(data, function(o) { self.map_currency_id_rounding[o.id] = o.rounding });
+ });
+
// Create a dict tax id -> amount
new instance.web.Model("account.tax")
.query(['id', 'amount'])
self.$(".reconciliation_lines_container").fadeIn(self.aestetic_animation_speed);
});
});
- }
- // Congratulate the user if the work is done
- if (self.reconciled_lines === self.st_lines.length) {
+ } else if (self.reconciled_lines === self.st_lines.length) {
+ // Congratulate the user if the work is done
self.displayDoneMessage();
+ } else {
+ // Some lines weren't persisted because they were't valid
+ self.$(".reconciliation_lines_container").fadeIn(self.aestetic_animation_speed);
}
}).fail(function() {
self.$(".reconciliation_lines_container").fadeIn(self.aestetic_animation_speed);
// Update children if needed
_.each(self.getChildren(), function(child){
- if (child.partner_id === partner_id && child !== source_child) {
+ if ((child.partner_id === partner_id || child.st_line.has_no_partner) && child !== source_child) {
if (contains_lines(child.get("mv_lines_selected"), line_ids)) {
child.set("mv_lines_selected", _.filter(child.get("mv_lines_selected"), function(o){ return line_ids.indexOf(o.id) === -1 }));
} else if (contains_lines(child.mv_lines_deselected, line_ids)) {
_.each(self.getChildren(), function(child){
if (child.partner_id === partner_id && child !== source_child && (child.get("mode") === "match" || child.$el.hasClass("no_match")))
child.updateMatches();
+ if (child.st_line.has_no_partner && child.get("mode") === "match" || child.$el.hasClass("no_match"))
+ child.updateMatches();
});
},
this.model_bank_statement_line = new instance.web.Model("account.bank.statement.line");
this.model_res_users = new instance.web.Model("res.users");
this.model_tax = new instance.web.Model("account.tax");
+ this.map_currency_id_rounding = this.getParent().map_currency_id_rounding;
this.map_account_id_code = this.getParent().map_account_id_code;
this.map_tax_id_amount = this.getParent().map_tax_id_amount;
this.presets = this.getParent().presets;
this.is_valid = true;
this.is_consistent = true; // Used to prevent bad server requests
- this.total_move_lines_num = undefined; // Used for pagers
+ this.can_fetch_more_move_lines; // Tell if we can show more move lines
this.filter = "";
// In rare cases like when deleting a statement line's partner we don't want the server to
// look for a reconciliation proposition (in this particular case it might find a move line
pagerControlLeftHandler: function() {
var self = this;
if (self.$(".pager_control_left").hasClass("disabled")) { return; /* shouldn't happen, anyway*/ }
- if (self.total_move_lines_num < 0) { return; }
+ if (self.get("pager_index") === 0) { return; }
self.set("pager_index", self.get("pager_index")-1 );
},
pagerControlRightHandler: function() {
var self = this;
- var new_index = self.get("pager_index")+1;
if (self.$(".pager_control_right").hasClass("disabled")) { return; /* shouldn't happen, anyway*/ }
- if ((new_index * self.max_move_lines_displayed) >= self.total_move_lines_num) { return; }
- self.set("pager_index", new_index );
+ if (! self.can_fetch_more_move_lines) { return; }
+ self.set("pager_index", self.get("pager_index")+1 );
},
filterHandler: function() {
self.$(".pager_control_left").addClass("disabled");
else
self.$(".pager_control_left").removeClass("disabled");
- if (self.total_move_lines_num <= ((self.get("pager_index")+1) * self.max_move_lines_displayed))
+ if (! self.can_fetch_more_move_lines)
self.$(".pager_control_right").addClass("disabled");
else
self.$(".pager_control_right").removeClass("disabled");
mvLinesChanged: function() {
var self = this;
// If pager_index is out of range, set it to display the last page
- if (self.get("pager_index") !== 0 && self.total_move_lines_num <= (self.get("pager_index") * self.max_move_lines_displayed)) {
- self.set("pager_index", Math.ceil(self.total_move_lines_num/self.max_move_lines_displayed)-1);
+ if (self.get("pager_index") !== 0 && self.get("mv_lines").length === 0 && ! self.can_fetch_more_move_lines) {
+ self.set("pager_index", 0);
}
// If there is no match to display, disable match view and pass in mode inactive
- if (self.total_move_lines_num + self.mv_lines_deselected.length === 0 && self.filter === "") {
+ if (self.get("mv_lines").length + self.mv_lines_deselected.length === 0 && !self.can_fetch_more_move_lines && self.filter === "") {
self.$el.addClass("no_match");
if (self.get("mode") === "match") {
self.set("mode", "inactive");
}
);
} else {
- line_created_being_edited[0].amount = amount;
line_created_being_edited.length = 1;
deferred_tax.resolve();
}
$.when(deferred_tax).then(function(){
// Format amounts
+ var rounding = 1/self.map_currency_id_rounding[self.st_line.currency_id];
$.each(line_created_being_edited, function(index, val) {
- if (val.amount)
+ if (val.amount) {
+ line_created_being_edited[index].amount = Math.round(val.amount*rounding)/rounding;
line_created_being_edited[index].amount_str = self.formatCurrency(Math.abs(val.amount), val.currency_id);
+ }
});
self.set("line_created_being_edited", line_created_being_edited);
self.createdLinesChanged(); // TODO For some reason, previous line doesn't trigger change handler
updateMatches: function() {
var self = this;
var deselected_lines_num = self.mv_lines_deselected.length;
- var move_lines_num = 0;
var offset = self.get("pager_index") * self.max_move_lines_displayed - deselected_lines_num;
if (offset < 0) offset = 0;
var limit = (self.get("pager_index")+1) * self.max_move_lines_displayed - deselected_lines_num;
if (limit > self.max_move_lines_displayed) limit = self.max_move_lines_displayed;
- var excluded_ids = self.getParent().excluded_move_lines_ids[self.partner_id];
var excluded_ids = _.collect(self.get("mv_lines_selected").concat(self.mv_lines_deselected), function(o) { return o.id; });
- var globally_excluded_ids = self.getParent().excluded_move_lines_ids[self.partner_id];
+ var globally_excluded_ids = [];
+ if (self.st_line.has_no_partner)
+ _.each(self.getParent().excluded_move_lines_ids, function(o) { globally_excluded_ids = globally_excluded_ids.concat(o) });
+ else
+ globally_excluded_ids = self.getParent().excluded_move_lines_ids[self.partner_id];
if (globally_excluded_ids !== undefined)
for (var i=0; i<globally_excluded_ids.length; i++)
if (excluded_ids.indexOf(globally_excluded_ids[i]) === -1)
excluded_ids.push(globally_excluded_ids[i]);
- var deferred_move_lines;
- var move_lines = [];
+ limit += 1; // Let's fetch 1 more item than requested
if (limit > 0) {
- // Load move lines
- deferred_move_lines = self.model_bank_statement_line
+ return self.model_bank_statement_line
.call("get_move_lines_for_reconciliation_by_statement_line_id", [self.st_line.id, excluded_ids, self.filter, offset, limit])
.then(function (lines) {
- _.each(lines, function(line) {
- self.decorateMoveLine(line, self.st_line.currency_id);
- move_lines.push(line);
- }, self);
+ _.each(lines, function(line) { self.decorateMoveLine(line, self.st_line.currency_id) }, self);
+ // If we could fetch 1 more item than what we'll display, that means there are move lines left to be displayed (so we enable the pager)
+ self.can_fetch_more_move_lines = (lines.length === limit);
+ self.set("mv_lines", lines.slice(0, limit-1));
});
+ } else {
+ self.set("mv_lines", []);
}
-
- // Fetch the number of move lines corresponding to this statement line and this filter
- var deferred_total_move_lines_num = self.model_bank_statement_line
- .call("get_move_lines_for_reconciliation_by_statement_line_id", [self.st_line.id, excluded_ids, self.filter, 0, undefined, true])
- .then(function(num){
- move_lines_num = num;
- });
-
- return $.when(deferred_move_lines, deferred_total_move_lines_num).then(function(){
- self.total_move_lines_num = move_lines_num + deselected_lines_num;
- self.set("mv_lines", move_lines);
- });
},
// Changes the partner_id of the statement_line in the DB and reloads the widget
- changePartner: function(partner_id, callback) {
+ changePartner: function(partner_id) {
var self = this;
self.is_consistent = false;
return self.model_bank_statement_line
self.do_load_reconciliation_proposition = true;
self.is_consistent = true;
self.set("mode", "match");
- if (callback) callback();
});
});
},
# when required, make sure the partner has a valid signup token
if context.get('signup_valid') and not partner.user_ids:
self.signup_prepare(cr, uid, [partner.id], context=context)
- partner.refresh()
route = 'login'
# the parameters to encode for the query
# _mail_flat_thread: automatically set free messages to the first posted message
if self._mail_flat_thread and model and not parent_id and thread_id:
- message_ids = mail_message.search(cr, uid, ['&', ('res_id', '=', thread_id), ('model', '=', model)], context=context, order="id ASC", limit=1)
+ message_ids = mail_message.search(cr, uid, ['&', ('res_id', '=', thread_id), ('model', '=', model), ('type', '=', 'email')], context=context, order="id ASC", limit=1)
+ if not message_ids:
+ message_ids = message_ids = mail_message.search(cr, uid, ['&', ('res_id', '=', thread_id), ('model', '=', model)], context=context, order="id ASC", limit=1)
parent_id = message_ids and message_ids[0] or False
# we want to set a parent: force to set the parent_id to the oldest ancestor, to avoid having more than 1 level of thread
elif parent_id:
new_moves = stock_mov_obj.action_consume(cr, uid, [produce_product.id], (subproduct_factor * production_qty_uom),
location_id=produce_product.location_id.id, restrict_lot_id=lot_id, context=context)
stock_mov_obj.write(cr, uid, new_moves, {'production_id': production_id}, context=context)
- if produce_product.product_id.id == production.product_id.id and new_moves:
- main_production_move = new_moves[0]
+ if produce_product.product_id.id == production.product_id.id:
+ main_production_move = produce_product.id
if production_mode in ['consume', 'consume_produce']:
if wiz:
def _make_production_produce_line(self, cr, uid, production, context=None):
stock_move = self.pool.get('stock.move')
+ proc_obj = self.pool.get('procurement.order')
source_location_id = production.product_id.property_stock_production.id
destination_location_id = production.location_dest_id.id
+ procs = proc_obj.search(cr, uid, [('production_id', '=', production.id)], context=context)
+ procurement_id = procs and procs[0] or False
data = {
'name': production.name,
'date': production.date_planned,
'location_id': source_location_id,
'location_dest_id': destination_location_id,
'move_dest_id': production.move_prod_id.id,
+ 'procurement_id': procurement_id,
'company_id': production.company_id.id,
'production_id': production.id,
'origin': production.name,
""" Confirms production order and calculates quantity based on subproduct_type.
@return: Newly generated picking Id.
"""
+ move_obj = self.pool.get('stock.move')
picking_id = super(mrp_production,self).action_confirm(cr, uid, ids, context=context)
product_uom_obj = self.pool.get('product.uom')
for production in self.browse(cr, uid, ids):
'location_id': source,
'location_dest_id': production.location_dest_id.id,
'move_dest_id': production.move_prod_id.id,
- 'state': 'waiting',
'production_id': production.id
}
- self.pool.get('stock.move').create(cr, uid, data)
+ move_id = move_obj.create(cr, uid, data, context=context)
+ move_obj.action_confirm(cr, uid, [move_id], context=context)
+
return picking_id
def _get_subproduct_factor(self, cr, uid, production_id, move_id=None, context=None):
return False
return True
+ def _check_company_payment(self, cr, uid, ids, context=None):
+ for config in self.browse(cr, uid, ids, context=context):
+ journal_ids = [j.id for j in config.journal_ids]
+ if self.pool['account.journal'].search(cr, uid, [
+ ('id', 'in', journal_ids),
+ ('company_id', '!=', config.company_id.id)
+ ], count=True, context=context):
+ return False
+ return True
+
_constraints = [
(_check_cash_control, "You cannot have two cash controls in one Point Of Sale !", ['journal_ids']),
(_check_company_location, "The company of the stock location is different than the one of point of sale", ['company_id', 'stock_location_id']),
(_check_company_journal, "The company of the sale journal is different than the one of point of sale", ['company_id', 'journal_id']),
+ (_check_company_payment, "The company of a payment method is different than the one of point of sale", ['company_id', 'journal_ids']),
]
def name_get(self, cr, uid, ids, context=None):
sum(l.qty * u.factor) as product_qty,
sum(l.qty * l.price_unit) as price_total,
sum((l.qty * l.price_unit) * (l.discount / 100)) as total_discount,
- (sum(l.qty*l.price_unit)/sum(l.qty * u.factor))::decimal(16,2) as average_price,
+ (sum(l.qty*l.price_unit)/sum(l.qty * u.factor))::decimal as average_price,
sum(cast(to_char(date_trunc('day',s.date_order) - date_trunc('day',s.create_date),'DD') as int)) as delay_validation,
s.partner_id as partner_id,
s.state as state,
c.height = height
var ctx = c.getContext('2d');
ctx.drawImage(self.company_logo,0,0, width, height);
-
+
self.company_logo_base64 = c.toDataURL();
logo_loaded.resolve();
};
self.company_logo.onerror = function(){
logo_loaded.reject();
};
+ self.company_logo.crossOrigin = "anonymous";
self.company_logo.src = '/web/binary/company_logo' +'?_'+Math.random();
return logo_loaded;
planned_hours as hours_planned,
(extract('epoch' from (t.write_date-t.create_date)))/(3600*24) as closing_days,
(extract('epoch' from (t.date_start-t.create_date)))/(3600*24) as opening_days,
- abs((extract('epoch' from (t.date_deadline-t.write_date)))/(3600*24)) as delay_endings_days
+ (extract('epoch' from (t.date_deadline-now())))/(3600*24) as delay_endings_days
FROM project_task t
WHERE t.active = 'true'
GROUP BY
product_uom = self.pool.get('product.uom')
price_unit = order_line.price_unit
if order_line.product_uom.id != order_line.product_id.uom_id.id:
- price_unit *= order_line.product_uom.factor
+ price_unit *= order_line.product_uom.factor / order_line.product_id.uom_id.factor
if order.currency_id.id != order.company_id.currency_id.id:
#we don't round the price_unit, as we may want to store the standard price with more digits than allowed by the currency
price_unit = self.pool.get('res.currency').compute(cr, uid, order.currency_id.id, order.company_id.currency_id.id, price_unit, round=False, context=context)
'product_id': _get_advance_product,
}
+ def _translate_advance(self, cr, uid, percentage=False, context=None):
+ return _("Advance of %s %%") if percentage else _("Advance of %s %s")
+
def onchange_method(self, cr, uid, ids, advance_payment_method, product_id, context=None):
if advance_payment_method == 'percentage':
return {'value': {'amount':0, 'product_id':False }}
if wizard.advance_payment_method == 'percentage':
inv_amount = sale.amount_total * wizard.amount / 100
if not res.get('name'):
- res['name'] = _("Advance of %s %%") % (wizard.amount)
+ res['name'] = self._translate_advance(cr, uid, percentage=True, context=dict(context, lang=sale.partner_id.lang)) % (wizard.amount)
else:
inv_amount = wizard.amount
if not res.get('name'):
#TODO: should find a way to call formatLang() from rml_parse
symbol = sale.pricelist_id.currency_id.symbol
if sale.pricelist_id.currency_id.position == 'after':
- res['name'] = _("Advance of %s %s") % (inv_amount, symbol)
+ symbol_order = (inv_amount, symbol)
else:
- res['name'] = _("Advance of %s %s") % (symbol, inv_amount)
+ symbol_order = (symbol, inv_amount)
+ res['name'] = self._translate_advance(cr, uid, context=dict(context, lang=sale.partner_id.lang)) % symbol_order
# determine taxes
if res.get('invoice_line_tax_id'):
sale_obj.write(cr, uid, sale_id, {'invoice_ids': [(4, inv_id)]}, context=context)
return inv_id
-
def create_invoices(self, cr, uid, ids, context=None):
""" create invoices for the active sales orders """
sale_obj = self.pool.get('sale.order')
'/web/binary/company_logo',
'/logo',
'/logo.png',
- ], type='http', auth="none")
+ ], type='http', auth="none", cors="*")
def company_logo(self, dbname=None, **kw):
imgname = 'logo.png'
placeholder = functools.partial(get_module_resource, 'web', 'static', 'src', 'img')
_name = 'website.qweb.field.contact'
_inherit = ['ir.qweb.field.contact', 'website.qweb.field.many2one']
+ def from_html(self, cr, uid, model, column, element, context=None):
+ return None
+
class QwebView(orm.AbstractModel):
_name = 'website.qweb.field.qweb'
_inherit = ['ir.qweb.field.qweb']
this.changed($(e.target));
},
'click button.filepicker': function () {
- this.$('input[type=file]').click();
+ var filepicker = this.$('input[type=file]');
+ if (!_.isEmpty(filepicker)){
+ filepicker[0].click();
+ }
},
'click .js_disable_optimization': function () {
this.$('input[name="disable_optimization"]').val('1');
- this.$('button.filepicker').click();
+ var filepicker = this.$('button.filepicker');
+ if (!_.isEmpty(filepicker)){
+ filepicker[0].click();
+ }
},
'change input[type=file]': 'file_selection',
'submit form': 'form_submit',
<a href="/page/website.contactus" class="btn btn-success btn-large">Contact us</a>
</p>
</div>
- <span class="carousel-img col-md-6 hidden-sm hidden-xs">
+ <div class="carousel-img col-md-6 hidden-sm hidden-xs">
<img class="img-responsive" src="/website/static/src/img/banner/banner_picture.png" alt="Banner Odoo Image"/>
- </span>
+ </div>
</div>
</div>
</div>
cr, uid, context = request.cr, request.uid, request.context
if kwargs.get('comment') and post.forum_id.id == forum.id:
# TDE FIXME: check that post_id is the question or one of its answers
- request.registry['forum.post'].message_post(
- cr, uid, post.id,
+ request.registry['forum.post']._post_comment(
+ cr, uid, post,
body=kwargs.get('comment'),
- type='comment',
- subtype='mt_comment',
- context=dict(context, mail_create_nosubcribe=True))
+ context=context)
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True)
from openerp.tools import html2plaintext
from openerp.tools.translate import _
+from werkzeug.exceptions import Forbidden
-class KarmaError(ValueError):
+class KarmaError(Forbidden):
""" Karma-related error, used for forum and posts. """
pass
context = {}
create_context = dict(context, mail_create_nolog=True)
post_id = super(Post, self).create(cr, uid, vals, context=create_context)
- post = self.browse(cr, SUPERUSER_ID, post_id, context=context) # SUPERUSER_ID to avoid read access rights issues when creating
+ post = self.browse(cr, uid, post_id, context=context)
# deleted or closed questions
if post.parent_id and (post.parent_id.state == 'close' or post.parent_id.active == False):
osv.except_osv(_('Error !'), _('Posting answer on [Deleted] or [Closed] question is prohibited'))
# karma-based access
- if post.parent_id and not post.can_ask:
+ if not post.parent_id and not post.can_ask:
raise KarmaError('Not enough karma to create a new question')
- elif not post.parent_id and not post.can_answer:
+ elif post.parent_id and not post.can_answer:
raise KarmaError('Not enough karma to answer to a question')
# messaging and chatter
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
res_id = post.parent_id and "%s#answer-%s" % (post.parent_id.id, post.id) or post.id
return "/forum/%s/question/%s" % (post.forum_id.id, res_id)
+ def _post_comment(self, cr, uid, post, body, context=None):
+ context = dict(context or {}, mail_create_nosubcribe=True)
+ if not post.can_comment:
+ raise KarmaError('Not enough karma to comment')
+ return self.message_post(cr, uid, post.id,
+ body=body,
+ type='comment',
+ subtype='mt_comment',
+ context=context)
class PostReason(osv.Model):
_name = "forum.post.reason"
<t t-raw="0"/>
</div>
<div class="col-sm-3" id="right-column">
- <div t-if="not header.get('ask_hide')" class="btn-group btn-block mb16">
+ <div t-if="not header.get('ask_hide')" t-attf-class="btn-group btn-block mb16 #{user.karma >= forum.karma_ask and '' or 'karma_required'}" t-attf-data-karma="#{forum.karma_ask}">
<a type="button" class="btn btn-primary btn-lg col-sm-10" t-attf-href="/forum/#{slug(forum)}/#{forum.default_allow}">
<t t-if="forum.default_allow == 'ask_question'">Ask a Question</t>
<t t-if="forum.default_allow == 'post_link'">Submit a Post</t>
<br/>
<input type="text" name="post_tags" placeholder="Tags" class="form-control load_tags"/>
<br/>
- <button class="btn btn-primary" id="btn_ask_your_question">Post Your Question</button>
+ <button t-attf-class="btn btn-primary #{(user.karma <= forum.karma_ask) and 'karma_required' or ''}"
+ id="btn_ask_your_question" t-att-data-karma="forum.karma_ask">Post Your Question</button>
</form>
</t>
</template>
<form t-attf-action="/forum/#{ slug(forum) }/#{slug(question)}/reply" method="post" role="form">
<input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
<textarea name="content" t-attf-id="content-#{str(question.id)}" class="form-control load_editor" required="True"/>
- <button class="btn btn-primary mt16" id="btn_ask_your_question">Post Your Reply</button>
+ <button t-attf-class="btn btn-primary mt16 #{not question.can_answer and 'karma_required' or ''}"
+ id="btn_ask_your_question" t-att-data-karma="question.karma_answer">Post Your Reply</button>
</form>
</template>
</div>
<ul class="list-inline" id="options">
<li t-if="question.type == 'question'">
- <a style="cursor: pointer" data-toggle="collapse"
+ <a style="cursor: pointer" t-att-data-toggle="question.can_comment and 'collapse' or ''"
t-attf-class="fa fa-comment-o #{not question.can_comment and 'karma_required text-muted' or ''}"
t-attf-data-karma="#{not question.can_comment and question.karma_comment or 0}"
t-attf-data-target="#comment#{ question._name.replace('.','') + '-' + str(question.id) }">
<li t-if="question.type == 'question'">
<a t-attf-class="fa fa-comment-o #{not answer.can_comment and 'karma_required text-muted' or ''}"
t-attf-data-karma="#{not answer.can_comment and answer.karma_comment or 0}"
- style="cursor: pointer" data-toggle="collapse"
+ style="cursor: pointer" t-att-data-toggle="answer.can_comment and 'collapse' or ''"
t-attf-data-target="#comment#{ answer._name.replace('.','') + '-' + str(answer.id) }"> Comment
</a>
</li>
class product_attribute(osv.Model):
_inherit = "product.attribute"
_columns = {
- 'type': fields.selection([('radio', 'Radio'), ('select', 'Select'), ('color', 'Color'), ('hidden', 'Hidden')], string="Type", type="char"),
+ 'type': fields.selection([('radio', 'Radio'), ('select', 'Select'), ('color', 'Color'), ('hidden', 'Hidden')], string="Type"),
}
_defaults = {
'type': lambda *a: 'radio',
var $parent = $(this).closest('.js_product');
$parent.find(".oe_default_price:first .oe_currency_value").html( price_to_str(+$(this).data('lst_price')) );
$parent.find(".oe_price:first .oe_currency_value").html(price_to_str(+$(this).data('price')) );
+
+ var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img, img.product_detail_img');
+ $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) {
}
if (product_id) {
- var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img');
+ var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img, img.product_detail_img');
$img.attr("src", "/website/image/product.product/" + product_id + "/image");
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
.data('oe-model', 'product.product').data('oe-id', product_id);
$select.find("option:not(:first)").hide();
var nb = $select.find("option[data-country_id="+($(this).val() || 0)+"]").show().size();
$select.parent().toggle(nb>1);
- }).change();
+ });
+ $(oe_website_sale).find("select[name='country_id']").change();
+
$(oe_website_sale).on('change', "select[name='shipping_country_id']", function () {
var $select = $("select[name='shipping_state_id']");
$select.find("option:not(:first)").hide();
var nb = $select.find("option[data-country_id="+($(this).val() || 0)+"]").show().size();
$select.parent().toggle(nb>1);
- }).change();
+ });
+ $(oe_website_sale).find("select[name='shipping_country_id']").change();
});
});
</field>
</field>
</record>
+ <record id="attribute_tree_view" model="ir.ui.view">
+ <field name="name">product.attribute.tree.type</field>
+ <field name="model">product.attribute</field>
+ <field name="inherit_id" ref="product.attribute_tree_view"></field>
+ <field name="arch" type="xml">
+ <field name="name" position="after">
+ <field name="type"/>
+ </field>
+ </field>
+ </record>
<!-- Product Public Categories -->
<record id="product_public_category_form_view" model="ir.ui.view">
.click(function (event) {
var $form = $(this).closest('form');
var quantity = parseFloat($form.find('input[name="add_qty"]').val() || 1);
+ var product_id = parseInt($form.find('input[type="hidden"][name="product_id"], input[type="radio"][name="product_id"]:checked').first().val(),10);
event.preventDefault();
openerp.jsonRpc("/shop/modal", 'call', {
- 'product_id': parseInt($form.find('input[name="product_id"]').val(),10),
+ 'product_id': product_id,
kwargs: {
context: openerp.website.get_context()
},
}).then(function (modal) {
var $modal = $(modal);
+ $modal.find('img:first').attr("src", "/website/image/product.product/" + product_id + "/image");
+
$modal.appendTo($form)
.modal()
.on('hidden.bs.modal', function () {
main_navbar=False, titles_only=False) }}
{% if github_link %}
<p><a href="{{ github_link() }}" class="github">
- Edit on GitHub
+ View on GitHub
</a></p>
{% endif %}
</div>
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
+ 'sphinx.ext.ifconfig',
'sphinx.ext.todo',
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
+# default must be set otherwise ifconfig blows up
+todo_include_todos = False
+
intersphinx_mapping = {
'python': ('https://docs.python.org/2/', None),
'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
odoo developer documentation
============================
-.. TODO: replace or style
+Welcome to the Odoo developer documentation.
+
+This documentation is incomplete and may contain errors, if you wish to
+contribute, every page should have a :guilabel:`View on Github` link:
+
+.. image:: images/view-on-github.*
+ :align: center
+
+Through this link you can edit documents and submit changes for review using
+`github's web interface
+<https://help.github.com/articles/editing-files-in-your-repository/>`_.
+Contributions are welcome and appreciated.
+
+.. todo:: what's the documentation's license?
+
+The documentation is currently organized in four sections:
+
+* :doc:`tutorials`, aimed at introducing the primary areas of developing Odoo
+ modules
+* :doc:`guides`, didactic documents covering more specific and specialized
+ areas of Odoo, trying to solve more specific problems
+* :doc:`reference`, which ought be the complete and canonical documentation
+ for Odoo subsystems
+* :doc:`modules`, documenting useful specialized modules and integration
+ methods (and currently empty)
.. hidden toctree w/o titlesonly otherwise the titlesonly "sticks" to
in-document toctrees and we can't have a toctree showing both "sibling"
reference
modules
-.. todolist::
+.. ifconfig:: todo_include_todos
+
+ .. rubric:: Things to add and fix
+
+ .. todolist::
.. toctree::
:titlesonly:
- modules/mail
+++ /dev/null
-============
-Mail Threads
-============
-
-
database used when installing or updating modules.
+.. option:: --db-filter=<filter>
+
+ hides databases that do not match ``<filter>``. The filter is a
+ `regular expression`_, with the additions that:
+
+ - ``%h`` is replaced by the whole hostname the request is made on.
+ - ``%d`` is replaced by the subdomain the request is made on, with the
+ exception of ``www`` (so domain ``odoo.com`` and ``www.odoo.com`` both
+ match the database ``odoo``)
+
.. option:: -i <modules>, --init=<modules>
- comma-separated list of modules to install before running the server.
+ comma-separated list of modules to install before running the server
+ (requires :option:`-d`).
.. option:: -u <modules>, --update=<modules>
- comma-separated list of modules to update before running the server.
+ comma-separated list of modules to update before running the server
+ (requires :option:`-d`).
-.. option:: --addons-path <directories>
+.. option:: --addons-path=<directories>
comma-separated list of directories in which modules are stored. These
directories are scanned for modules (nb: when and why?)
-.. option:: -c <config>, --config <config>
+.. option:: -c <config>, --config=<config>
provide an alternate configuration file
to that file.
.. _jinja2: http://jinja.pocoo.org
+.. _regular expression: https://docs.python.org/2/library/re.html
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
# All other exceptions mean undetermined status (e.g. connection pool full),
# let them bubble up
- request.session.logout()
+ request.session.logout(keep_db=True)
getattr(self, "_auth_method_%s" % auth_method)()
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
raise
change_default=True, domain="[('country_id','=',country_id)]"),
'company_id': fields.many2one('res.company', 'Company',
ondelete='cascade', help="Only if this bank account belong to your company"),
- 'partner_id': fields.many2one('res.partner', 'Account Owner', ondelete='cascade', select=True),
+ 'partner_id': fields.many2one('res.partner', 'Account Owner', ondelete='cascade', select=True, domain=['|',('is_company','=',True),('parent_id','=',False)]),
'state': fields.selection(_bank_type_get, 'Bank Account Type', required=True,
change_default=True),
'sequence': fields.integer('Sequence'),
'date': fields.date('Date', select=1),
'title': fields.many2one('res.partner.title', 'Title'),
'parent_id': fields.many2one('res.partner', 'Related Company', select=True),
+ 'parent_name': fields.related('parent_id', 'name', type='char', readonly=True, string='Parent name'),
'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts', domain=[('active','=',True)]), # force "active_test" domain to bypass _search() override
'ref': fields.char('Internal Reference', select=1),
'lang': fields.selection(_lang_get, 'Language',
for record in self.browse(cr, uid, ids, context=context):
name = record.name
if record.parent_id and not record.is_company:
- name = "%s, %s" % (record.parent_id.name, name)
+ name = "%s, %s" % (record.parent_name, name)
if context.get('show_address_only'):
name = self._display_address(cr, uid, record, without_company=True, context=context)
if context.get('show_address'):
'state_name': address.state_id.name or '',
'country_code': address.country_id.code or '',
'country_name': address.country_id.name or '',
- 'company_name': address.parent_id.name or '',
+ 'company_name': address.parent_name or '',
}
for field in self._address_fields(cr, uid, context=context):
args[field] = getattr(address, field) or ''