self.create(cr, uid, data, context)
del vals['account_tax_id']
- if check and not context.get('novalidate') and ((not context.get('no_store_function')) or journal.entry_posted):
+ if check and not context.get('novalidate') and (context.get('recompute', True) or journal.entry_posted):
tmp = move_obj.validate(cr, uid, [vals['move_id']], context)
if journal.entry_posted and tmp:
move_obj.button_validate(cr,uid, [vals['move_id']], context)
for po in self.browse(cr, uid, ids, context=context):
if po.allow_reorder:
continue
- todo = po.move_lines
+ todo = list(po.move_lines)
dt = datetime.strptime(po.date_start,'%Y-%m-%d %H:%M:%S')
while todo:
l = todo.pop(0)
report = report_obj._get_report_from_name(cr, uid, 'account.report_invoice')
selected_orders = posorder_obj.browse(cr, uid, ids, context=context)
+ ids_to_print = []
invoiced_posorders_ids = []
for order in selected_orders:
if order.invoice_id:
+ ids_to_print.append(order.invoice_id.id)
invoiced_posorders_ids.append(order.id)
not_invoiced_orders_ids = list(set(ids) - set(invoiced_posorders_ids))
raise osv.except_osv(_('Error!'), _('No link to an invoice for %s.' % ', '.join(not_invoiced_orders_names)))
docargs = {
- 'doc_ids': ids,
+ 'doc_ids': ids_to_print,
'doc_model': report.model,
'docs': selected_orders,
}
this.cache[store] = data;
},
_product_search_string: function(product){
- var str = '' + product.id + ':' + product.name;
+ var str = '' + product.id + ':' + product.display_name;
if(product.ean13){
str += '|' + product.ean13;
}
domain: function(self){ return [['state','=','opened'],['user_id','=',self.session.uid]]; },
loaded: function(self,pos_sessions){
self.pos_session = pos_sessions[0];
+ self.pos_session_id = parseInt(self.pos_session.name.split('/')[1]);
var orders = self.db.get_orders();
for (var i = 0; i < orders.length; i++) {
'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description',
'product_tmpl_id'],
domain: function(self){ return [['sale_ok','=',true],['available_in_pos','=',true]]; },
- context: function(self){ return { pricelist: self.pricelist.id }; },
+ context: function(self){ return { pricelist: self.pricelist.id, display_default_code: false }; },
loaded: function(self, products){
self.db.add_products(products);
},
unit_name: this.get_unit().name,
price: this.get_unit_price(),
discount: this.get_discount(),
- product_name: this.get_product().name,
+ product_name: this.get_product().display_name,
price_display : this.get_display_price(),
price_with_tax : this.get_price_with_tax(),
price_without_tax: this.get_price_without_tax(),
module.Order = Backbone.Model.extend({
initialize: function(attributes){
Backbone.Model.prototype.initialize.apply(this, arguments);
+ this.pos = attributes.pos;
+ this.sequence_number = this.pos.pos_session.sequence_number++;
this.uid = this.generateUniqueId();
this.set({
creationDate: new Date(),
name: "Order " + this.uid,
client: null,
});
- this.pos = attributes.pos;
this.selected_orderline = undefined;
this.selected_paymentline = undefined;
this.screen_data = {}; // see ScreenSelector
this.receipt_type = 'receipt'; // 'receipt' || 'invoice'
this.temporary = attributes.temporary || false;
- this.sequence_number = this.pos.pos_session.sequence_number++;
return this;
},
is_empty: function(){
return (this.get('orderLines').models.length === 0);
},
+ // Generates a public identification number for the order.
+ // The generated number must be unique and sequential. They are made 12 digit long
+ // to fit into EAN-13 barcodes.
generateUniqueId: function() {
- return new Date().getTime();
+ function zero_pad(num,size){
+ var s = ""+num;
+ while (s.length < size) {
+ s = "0" + s;
+ }
+ return s;
+ }
+ return zero_pad(this.pos.pos_session_id,6) + zero_pad(this.sequence_number,6);
},
addOrderline: function(line){
if(line.order){
},
get_product_name: function(){
var product = this.get_product();
- return (product ? product.name : undefined) || 'Unnamed Product';
+ return (product ? product.display_name : undefined) || 'Unnamed Product';
},
get_product_price: function(){
var product = this.get_product();
user = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context)
if user.share:
return self.pool['ir.actions.act_window'].for_xml_id(cr, uid, 'portal_sale', 'portal_action_invoices', context=context)
- return super(sale_order, self).get_formview_action(cr, uid, id, context=context)
+ return super(account_invoice, self).get_formview_action(cr, uid, id, context=context)
class mail_mail(osv.osv):
def _name_get(d):
name = d.get('name','')
- code = d.get('default_code',False)
+ code = context.get('display_default_code', True) and d.get('default_code',False) or False
if code:
name = '[%s] %s' % (code,name)
return (d['id'], name)
amount = vals_line['unit_amount']
prod_id = vals_line['product_id']
unit = False
- context = dict(context, no_store_function=False)
+ context = dict(context, recompute=True)
timeline_id = timesheet_obj.create(cr, uid, vals_line, context=context)
# Compute based on pricetype
<field name="name">product.template.purchase.button.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view"/>
+ <field name="groups_id" eval="[(4, ref('purchase.group_purchase_user'))]"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button class="oe_inline oe_stat_button" name="action_view_purchases" type="object"
- groups="purchase.group_purchase_user" icon="fa-shopping-cart">
+ icon="fa-shopping-cart">
<field string="Purchases" name="purchase_count" widget="statinfo"/>
</button>
</xpath>
<field name="name">product.product.purchase.button.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
+ <field name="groups_id" eval="[(4, ref('purchase.group_purchase_user'))]"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button class="oe_inline oe_stat_button" name="%(action_purchase_line_product_tree)d" type="action"
- groups="purchase.group_purchase_user" icon="fa-shopping-cart">
+ icon="fa-shopping-cart">
<field string="Purchases" name="purchase_count" widget="statinfo"/>
</button>
</xpath>
return grouplines(self, ordered_lines, sortkey)
+import openerp
+
class AccountInvoiceLine(osv.Model):
_inherit = 'account.invoice.line'
- _columns = {
- 'sale_layout_cat_id': fields.many2one('sale_layout.category',
- string='Section'),
- 'categ_sequence': fields.related('sale_layout_cat_id',
- 'sequence', type='integer',
- string='Layout Sequence', store=True)
- # Store is intentionally set in order to keep the "historic" order.
- }
_order = 'invoice_id, categ_sequence, sequence, id'
+ sale_layout_cat_id = openerp.fields.Many2one('sale_layout.category', string='Section')
+ categ_sequence = openerp.fields.Integer(related='sale_layout_cat_id.sequence',
+ string='Layout Sequence', store=True)
+
class SaleOrder(osv.Model):
_inherit = 'sale.order'
--- /dev/null
+.oe_toc_content img {
+ max-width: 600px;
+ height: auto !important;
+}
<data>
<!-- DOCUMENTATION TOC VIEWS -->
-
<record id="view_documentation_toc_list" model="ir.ui.view">
<field name="name">forum.documentation.toc.list</field>
<field name="model">forum.documentation.toc</field>
name="Documentation ToC" action="action_documentation_toc" sequence="20"/>
- <record id="view_documentation_toc_list" model="ir.ui.view">
- <field name="name">forum.documentation.toc.list</field>
- <field name="model">forum.documentation.toc</field>
- <field name="arch" type="xml">
- <tree string="Documentation TOC" editable="bottom">
- <field name="sequence" widget="handle"/>
- <field name="name"/>
- <field name="parent_id"/>
- </tree>
- </field>
- </record>
- <record id="action_documentation_toc" model="ir.actions.act_window">
- <field name="name">Documentation</field>
- <field name="res_model">forum.documentation.toc</field>
- <field name="view_type">form</field>
- <field name="view_mode">tree</field>
- </record>
- <menuitem id="menu_documentation"
- parent="website_forum.menu_website_forum" groups="base.group_user"
- name="Documentation ToC" action="action_documentation_toc" sequence="20"/>
-
-
<!-- Project Task Kanban View -->
<record model="ir.ui.view" id="view_forum_post_kanban">
<field name="name">forum.post.kanban</field>
<openerp>
<data>
+ <!-- Front-end assets: custom css -->
+ <template id="assets_frontend" inherit_id="website.assets_frontend" name="Shop">
+ <xpath expr="." position="inside">
+ <link rel='stylesheet' href='/website_forum_doc/static/src/css/website_forum_doc.css'/>
+ </xpath>
+ </template>
+
<!-- Layout add nav and footer -->
<template id="header_footer_custom" inherit_id="website.footer_default"
name="Footer Documentation Link">
<div class="row">
<div class="col-sm-9">
<h1 class="page-header" t-field="post.name"/>
- <blockquote t-if="bool(post.content)">
+ <blockquote class="oe_toc_content" t-if="bool(post.content)">
<t t-raw="post.content"/>
</blockquote>
'quote_id': fields.many2one('sale.quote.template', 'Quotation Template Reference', required=True, ondelete='cascade', select=True),
'name': fields.text('Description', required=True, translate=True),
'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True),
- 'website_description': fields.html('Line Description', translate=True),
+ 'website_description': fields.related('product_id', 'product_tmpl_id', 'quote_description', string='Line Description', type='html', translate=True),
'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Discount')),
'product_uom_qty': fields.float('Quantity', required=True, digits_compute= dp.get_precision('Product UoS')),
vals.update({
'price_unit': product_obj.list_price,
'product_uom_id': product_obj.uom_id.id,
- 'website_description': product_obj.website_description,
+ 'website_description': product_obj and (product_obj.quote_description or product_obj.website_description) or '',
'name': name,
})
return {'value': vals}
+ def _inject_quote_description(self, cr, uid, values, context=None):
+ values = dict(values or {})
+ if not values.get('website_description') and values.get('product_id'):
+ product = self.pool['product.product'].browse(cr, uid, values['product_id'], context=context)
+ values['website_description'] = product.quote_description or product.website_description or ''
+ return values
+
+ def create(self, cr, uid, values, context=None):
+ values = self._inject_quote_description(cr, uid, values, context)
+ ret = super(sale_quote_line, self).create(cr, uid, values, context=context)
+ # hack because create don t make the job for a related field
+ if values.get('website_description'):
+ self.write(cr, uid, ret, {'website_description': values['website_description']}, context=context)
+ return ret
+
+ def write(self, cr, uid, ids, values, context=None):
+ values = self._inject_quote_description(cr, uid, values, context)
+ return super(sale_quote_line, self).write(cr, uid, ids, values, context=context)
+
class sale_order_line(osv.osv):
_inherit = "sale.order.line"
'option_line_id': fields.one2many('sale.order.option', 'line_id', 'Optional Products Lines'),
}
- def _inject_website_description(self, cr, uid, values, context=None):
+ def _inject_quote_description(self, cr, uid, values, context=None):
values = dict(values or {})
if not values.get('website_description') and values.get('product_id'):
product = self.pool['product.product'].browse(cr, uid, values['product_id'], context=context)
- values['website_description'] = product.website_description
+ values['website_description'] = product.quote_description or product.website_description
return values
def create(self, cr, uid, values, context=None):
- values = self._inject_website_description(cr, uid, values, context)
- return super(sale_order_line, self).create(cr, uid, values, context=context)
+ values = self._inject_quote_description(cr, uid, values, context)
+ ret = super(sale_order_line, self).create(cr, uid, values, context=context)
+ # hack because create don t make the job for a related field
+ if values.get('website_description'):
+ self.write(cr, uid, ret, {'website_description': values['website_description']}, context=context)
+ return ret
def write(self, cr, uid, ids, values, context=None):
- values = self._inject_website_description(cr, uid, values, context)
+ values = self._inject_quote_description(cr, uid, values, context)
return super(sale_order_line, self).write(cr, uid, ids, values, context=context)
product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context)
vals.update({
'price_unit': product_obj.list_price,
- 'website_description': product_obj.product_tmpl_id.website_description,
+ 'website_description': product_obj.product_tmpl_id.quote_description,
'name': product_obj.name,
'uom_id': product_obj.product_tmpl_id.uom_id.id,
})
product_obj = self.pool.get('product.product').browse(cr, uid, product, context=context)
vals.update({
'price_unit': product_obj.list_price,
- 'website_description': product_obj.product_tmpl_id.website_description,
+ 'website_description': product_obj and (product_obj.quote_description or product_obj.website_description),
'name': product_obj.name,
'uom_id': product_obj.product_tmpl_id.uom_id.id,
})
class product_template(osv.Model):
_inherit = "product.template"
+
_columns = {
- 'website_description': fields.html('Description for the website'),
+ 'website_description': fields.html('Description for the website'), # hack, if website_sale is not installed
+ 'quote_description': fields.html('Description for the quote'),
}
+
# Use this:
# sudo gem install compass --pre
cmd_sass = ['sass', '--stdin', '-t', 'compressed', '--unix-newlines', '--compass', '-r', 'bootstrap-sass']
- cache = openerp.tools.lru.LRU(32)
rx_css_import = re.compile("(@import[^;{]+;?)", re.M)
rx_sass_import = re.compile("""(@import\s?['"]([^'"]+)['"])""")
rx_css_split = re.compile("\/\*\! ([a-f0-9-]+) \*\/")
return hashlib.sha1(check).hexdigest()
def js(self):
- key = 'js_%s' % self.xmlid
- if key in self.cache and self.cache[key][0] != self.version:
- # Invalidate cache on version mismach
- self.cache.pop(key)
- if key not in self.cache:
- content =';\n'.join(asset.minify() for asset in self.javascripts)
- self.cache[key] = (self.version, content)
- return self.cache[key][1]
+ content = self.get_cache('js')
+ if content is None:
+ content = ';\n'.join(asset.minify() for asset in self.javascripts)
+ self.set_cache('js', content)
+ return content
def css(self):
- key = 'css_%s' % self.xmlid
- if key in self.cache and self.cache[key][0] != self.version:
- # Invalidate cache on version mismach
- self.cache.pop(key)
- if key not in self.cache:
+ content = self.get_cache('css')
+ if content is None:
self.compile_sass()
content = '\n'.join(asset.minify() for asset in self.stylesheets)
content = u'\n'.join(matches)
if self.css_errors:
return content
- self.cache[key] = (self.version, content)
+ self.set_cache('css', content)
- return self.cache[key][1]
+ return content
+
+ def get_cache(self, type):
+ content = None
+ domain = [('url', '=', '/web/%s/%s/%s' % (type, self.xmlid, self.version))]
+ bundle = self.registry['ir.attachment'].search_read(self.cr, self.uid, domain, ['datas'], context=self.context)
+ if bundle:
+ content = bundle[0]['datas'].decode('base64')
+ return content
+
+ def set_cache(self, type, content):
+ ira = self.registry['ir.attachment']
+ url_prefix = '/web/%s/%s/' % (type, self.xmlid)
+ # Invalidate previous caches
+ oids = ira.search(self.cr, self.uid, [('url', '=like', url_prefix + '%')], context=self.context)
+ if oids:
+ ira.unlink(self.cr, openerp.SUPERUSER_ID, oids, context=self.context)
+ url = url_prefix + self.version
+ ira.create(self.cr, openerp.SUPERUSER_ID, dict(
+ datas=content.encode('utf8').encode('base64'),
+ type='binary',
+ name=url,
+ url=url,
+ ), context=self.context)
def css_message(self, message):
return """
# convert default values to the expected format
result = self._convert_to_write(result)
- for key, val in result.items():
- if isinstance(val, NewId):
- del result[key] # ignore new records in defaults
-
return result
def add_default_value(self, field):
pass
# check the cache, and update it if necessary
- if field not in self._cache:
+ if not self._cache.contains(field):
for values in result:
record = self.browse(values.pop('id'))
record._cache.update(record._convert_to_cache(values, validate=False))
- if field not in self._cache:
+ if not self._cache.contains(field):
e = AccessError("No value found for %s.%s" % (self, field.name))
self._cache[field] = FailedValue(e)
record_id = tocreate[table].pop('id', None)
- # When linking/creating parent records, force context without 'no_store_function' key that
- # defers stored functions computing, as these won't be computed in batch at the end of create().
- parent_context = dict(context)
- parent_context.pop('no_store_function', None)
-
if record_id is None or not record_id:
- record_id = self.pool[table].create(cr, user, tocreate[table], context=parent_context)
+ record_id = self.pool[table].create(cr, user, tocreate[table], context=context)
else:
- self.pool[table].write(cr, user, [record_id], tocreate[table], context=parent_context)
+ self.pool[table].write(cr, user, [record_id], tocreate[table], context=context)
updates.append((self._inherits[table], '%s', record_id))
# check Python constraints
recs._validate_fields(vals)
- if not context.get('no_store_function', False):
+ # invalidate and mark new-style fields to recompute
+ modified_fields = list(vals)
+ if self._log_access:
+ modified_fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
+ recs.modified(modified_fields)
+
+ if context.get('recompute', True):
result += self._store_get_values(cr, user, [id_new],
list(set(vals.keys() + self._inherits.values())),
context)
if not (model_name, ids, fields2) in done:
self.pool[model_name]._store_set_values(cr, user, ids, fields2, context)
done.append((model_name, ids, fields2))
-
# recompute new-style fields
- modified_fields = list(vals)
- if self._log_access:
- modified_fields += ['create_uid', 'create_date', 'write_uid', 'write_date']
- recs.modified(modified_fields)
recs.recompute()
- if self._log_create and not (context and context.get('no_store_function', False)):
+ if self._log_create and context.get('recompute', True):
message = self._description + \
" '" + \
self.name_get(cr, user, [id_new], context=context)[0][1] + \
cr.execute('update "' + self._table + '" set ' + \
'"'+f+'"='+self._columns[f]._symbol_set[0] + ' where id = %s', (self._columns[f]._symbol_set[1](value), id))
- # invalidate the cache for the modified fields
+ # invalidate and mark new-style fields to recompute
self.browse(cr, uid, ids, context).modified(fields)
return True
def _convert_to_write(self, values):
""" Convert the `values` dictionary into the format of :meth:`write`. """
fields = self._fields
- return dict(
- (name, fields[name].convert_to_write(value))
- for name, value in values.iteritems()
- if name in self._fields
- )
+ result = {}
+ for name, value in values.iteritems():
+ if name in fields:
+ value = fields[name].convert_to_write(value)
+ if not isinstance(value, NewId):
+ result[name] = value
+ return result
#
# Record traversal and update
def __init__(self, records):
self._recs = records
+ def contains(self, field):
+ """ Return whether `records[0]` has a value for `field` in cache. """
+ if isinstance(field, basestring):
+ field = self._recs._fields[field]
+ return self._recs.id in self._recs.env.cache[field]
+
def __contains__(self, field):
""" Return whether `records[0]` has a regular value for `field` in cache. """
if isinstance(field, basestring):
result = []
context = dict(context or {})
context.update(self._context)
- context['no_store_function'] = True
+ context['recompute'] = False # recomputation is done by outer create/write
if not values:
return
obj = obj.pool[self._obj]