diff options
| author | Christophe Simonis <chs@odoo.com> | 2015-05-11 15:12:24 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2015-05-11 15:12:24 +0200 |
| commit | 0da722aa6a3a5b2ea8a9f740ea2ccc5ed2260b0a (patch) | |
| tree | 9005a0ab8df9df374dda2d3cb4c8564bc73a023c /addons | |
| parent | d756bab4d1d89af414b80523124b463252f31159 (diff) | |
| parent | bff6dffbb424a9ab458ae8fc2044f0b52490c36b (diff) | |
[MERGE] forward port of branch 8.0 up to bff6dff
Diffstat (limited to 'addons')
42 files changed, 415 insertions, 123 deletions
diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index a3769e08818..896f359beae 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -20,6 +20,7 @@ ############################################################################## from openerp.osv import fields, osv, expression +from openerp.tools import float_is_zero from openerp.tools.translate import _ import openerp.addons.decimal_precision as dp from openerp.report import report_sxw @@ -597,7 +598,7 @@ class account_bank_statement_line(osv.osv): domain += [(amount_field, '<', 0), (amount_field, '>', (sign * amount))] else: domain += [(amount_field, '>', 0), (amount_field, '<', (sign * amount))] - mv_lines = self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, limit=5, additional_domain=domain) + mv_lines = self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, limit=5, additional_domain=domain, context=context) ret = [] total = 0 for line in mv_lines: @@ -841,6 +842,11 @@ class account_bank_statement_line(osv.osv): if mv_line_dict.get('counterpart_move_line_id'): #post an account line that use the same currency rate than the counterpart (to balance the account) and post the difference in another line ctx['date'] = mv_line.date + if mv_line.currency_id.id == mv_line_dict['currency_id'] \ + and float_is_zero(abs(mv_line.amount_currency) - abs(mv_line_dict['amount_currency']), precision_rounding=mv_line.currency_id.rounding): + debit_at_old_rate = mv_line.credit + credit_at_old_rate = mv_line.debit + else: debit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=ctx) credit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=ctx) mv_line_dict['credit'] = credit_at_old_rate diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 73ce713d994..2c4b9ef0bb1 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -131,7 +131,7 @@ class account_invoice(models.Model): continue # Get the correct line residual amount if line.currency_id == self.currency_id: - line_amount = line.currency_id and line.amount_residual_currency or line.amount_residual + line_amount = line.amount_residual_currency if line.currency_id else line.amount_residual else: from_currency = line.company_id.currency_id.with_context(date=line.date) line_amount = from_currency.compute(line.amount_residual, self.currency_id) diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 77a6ff2ed86..520b3ddad80 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -867,12 +867,17 @@ class account_move_line(osv.osv): total_amount_currency_str = rml_parser.formatLang(total_amount, currency_obj=line_currency) ret_line['credit_currency'] = actual_credit ret_line['debit_currency'] = actual_debit - ctx = context.copy() - if target_date: - ctx.update({'date': target_date}) - total_amount = currency_obj.compute(cr, uid, line_currency.id, target_currency.id, total_amount, context=ctx) - actual_debit = currency_obj.compute(cr, uid, line_currency.id, target_currency.id, actual_debit, context=ctx) - actual_credit = currency_obj.compute(cr, uid, line_currency.id, target_currency.id, actual_credit, context=ctx) + if target_currency == company_currency: + actual_debit = debit + actual_credit = credit + total_amount = debit or credit + else: + ctx = context.copy() + if target_date: + ctx.update({'date': target_date}) + total_amount = currency_obj.compute(cr, uid, line_currency.id, target_currency.id, total_amount, context=ctx) + actual_debit = currency_obj.compute(cr, uid, line_currency.id, target_currency.id, actual_debit, context=ctx) + actual_credit = currency_obj.compute(cr, uid, line_currency.id, target_currency.id, actual_credit, context=ctx) amount_str = rml_parser.formatLang(actual_debit or actual_credit, currency_obj=target_currency) total_amount_str = rml_parser.formatLang(total_amount, currency_obj=target_currency) diff --git a/addons/account/report/account_entries_report.py b/addons/account/report/account_entries_report.py index dd2f40560d0..9e042712ed0 100644 --- a/addons/account/report/account_entries_report.py +++ b/addons/account/report/account_entries_report.py @@ -134,7 +134,7 @@ class account_entries_report(osv.osv): l.amount_currency as amount_currency, l.debit as debit, l.credit as credit, - l.debit-l.credit as balance + coalesce(l.debit, 0.0) - coalesce(l.credit, 0.0) as balance from account_move_line l left join account_account a on (l.account_id = a.id) diff --git a/addons/account/static/src/js/account_widgets.js b/addons/account/static/src/js/account_widgets.js index 86a5172005d..da0e51a8680 100644 --- a/addons/account/static/src/js/account_widgets.js +++ b/addons/account/static/src/js/account_widgets.js @@ -236,7 +236,7 @@ openerp.account = function (instance) { // Display the reconciliations return self.model_bank_statement_line - .call("get_data_for_reconciliations", [reconciliations_to_show]) + .call("get_data_for_reconciliations", [reconciliations_to_show], {context:self.session.user_context}) .then(function (data) { var child_promises = []; while ((datum = data.shift()) !== undefined) @@ -1638,7 +1638,7 @@ openerp.account = function (instance) { limit += 1; // Let's fetch 1 more item than requested if (limit > 0) { 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]) + .call("get_move_lines_for_reconciliation_by_statement_line_id", [self.st_line.id, excluded_ids, self.filter, offset, limit], {context:self.session.user_context}) .then(function (lines) { _.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) diff --git a/addons/account/wizard/account_statement_from_invoice.py b/addons/account/wizard/account_statement_from_invoice.py index 658e1b4d829..e43988d1121 100644 --- a/addons/account/wizard/account_statement_from_invoice.py +++ b/addons/account/wizard/account_statement_from_invoice.py @@ -47,8 +47,8 @@ class account_statement_from_invoice_lines(osv.osv_memory): statement_obj = self.pool.get('account.bank.statement') statement_line_obj = self.pool.get('account.bank.statement.line') currency_obj = self.pool.get('res.currency') - line_date = time.strftime('%Y-%m-%d') statement = statement_obj.browse(cr, uid, statement_id, context=context) + line_date = statement.date # for each selected move lines for line in line_obj.browse(cr, uid, line_ids, context=context): diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index 90f81e8d884..28f5191666e 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -702,6 +702,9 @@ class calendar_event(osv.Model): val = pytz.UTC.localize(val) return val.astimezone(timezone) + if context is None: + context = {} + timezone = pytz.timezone(context.get('tz') or 'UTC') startdate = pytz.UTC.localize(datetime.strptime(event.start, DEFAULT_SERVER_DATETIME_FORMAT)) # Add "+hh:mm" timezone if not startdate: diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index b07b21e02a6..3fad50e1595 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -408,8 +408,8 @@ <field name="arch" type="xml"> <form string="Opportunities"> <header> - <button name="case_mark_won" string="Mark Won" type="object" class="oe_highlight"/> - <button name="case_mark_lost" string="Mark Lost" type="object" class="oe_highlight"/> + <button name="case_mark_won" string="Mark Won" attrs="{'invisible': [('probability', '=', 100)]}" type="object" class="oe_highlight"/> + <button name="case_mark_lost" string="Mark Lost" attrs="{'invisible': [('probability', '=', 100)]}" type="object" class="oe_highlight"/> <field name="stage_id" widget="statusbar" clickable="True" options="{'fold_field': 'fold'}" domain="['&', ('team_ids', '=', team_id), '|', ('type', '=', type), ('type', '=', 'both')]"/> diff --git a/addons/delivery/__openerp__.py b/addons/delivery/__openerp__.py index 1d1c59652c4..8a468af3431 100644 --- a/addons/delivery/__openerp__.py +++ b/addons/delivery/__openerp__.py @@ -41,7 +41,9 @@ invoices from picking, OpenERP is able to add and compute the shipping line. 'views/report_shipping.xml', ], 'demo': ['delivery_demo.xml'], - 'test': ['test/delivery_cost.yml'], + 'test': ['test/delivery_cost.yml', + 'test/stock_move_values_with_invoice_before_delivery.yml', + ], 'installable': True, 'auto_install': False, } diff --git a/addons/delivery/test/stock_move_values_with_invoice_before_delivery.yml b/addons/delivery/test/stock_move_values_with_invoice_before_delivery.yml new file mode 100644 index 00000000000..1a998f51e22 --- /dev/null +++ b/addons/delivery/test/stock_move_values_with_invoice_before_delivery.yml @@ -0,0 +1,83 @@ +- + test that the store fields of stock moves are computed with invoice before delivery flow +- + set a weight on ipod 16GB +- + !python {model: product.product, id: product.product_product_11}: | + self.write({'weight': 0.25, 'weight_net': 0.2}) +- + create a SO with invoice before delivery +- + !record {model: sale.order, id: sale_prepaid}: + partner_id: base.res_partner_18 + partner_invoice_id: base.res_partner_18 + partner_shipping_id: base.res_partner_18 + pricelist_id: product.list0 + order_policy: 'prepaid' + order_line: + - name: 'Ipod' + product_id: product.product_product_11 + product_uom_qty: 2 + product_uos_qty: 2 + product_uom: product.product_uom_unit + price_unit: 750.00 + carrier_id: normal_delivery_carrier +- + I add delivery cost in Sale order. +- + !python {model: sale.order, id: sale_prepaid}: | + self.delivery_set() +- + I confirm the SO. +- + !workflow {model: sale.order, action: order_confirm, ref: sale_prepaid} +- + I check that the invoice was created +- + !python {model: sale.order, id: sale_prepaid}: | + assert len(self.invoice_ids) == 1, "Invoice not created." +- + I confirm the invoice +- + !python {model: sale.order, id: sale_prepaid}: | + invoice = self.invoice_ids[0] + invoice.signal_workflow('invoice_open') +- + I pay the invoice. +- + !python {model: sale.order, id: sale_prepaid}: | + invoice = self.invoice_ids[0] + invoice.signal_workflow('invoice_open') + journal = self.env['account.journal'].search([('type', '=', 'cash'), + ('company_id', '=', self.company_id.id)], + limit=1) + invoice.pay_and_reconcile( + invoice.amount_total, ref('account.cash'), ref('account.period_8'), + journal.id, ref('account.cash'), + ref('account.period_8'), journal.id, + name='test') +- + Check the SO after paying the invoice +- + !python {model: sale.order, id: sale_prepaid}: | + assert self.invoice_exists, 'order not invoiced' + assert self.invoiced, 'order is not paid' + assert len(self.picking_ids) == 1, 'pickings not generated' +- + check the stock moves +- + !python {model: sale.order, id: sale_prepaid}: | + move = self.picking_ids.move_lines + assert move.product_qty == 2, 'wrong product_qty' + assert move.weight == 0.5, 'wrong move weight' + assert move.weight_net == 0.4, 'wrong move weight_net' +- + ship +- + !python {model: sale.order, id: sale_prepaid}: | + picking = self.picking_ids.action_done() +- + Check the SO after shipping +- + !python {model: sale.order, id: sale_prepaid}: | + assert self.state == 'done' diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py index 64f6cf0765c..337874cf67a 100644 --- a/addons/hw_proxy/controllers/main.py +++ b/addons/hw_proxy/controllers/main.py @@ -192,12 +192,12 @@ class Proxy(http.Controller): print 'print_receipt' + str(receipt) @http.route('/hw_proxy/is_scanner_connected', type='json', auth='none', cors='*') - def print_receipt(self, receipt): + def is_scanner_connected(self, receipt): print 'is_scanner_connected?' return False @http.route('/hw_proxy/scanner', type='json', auth='none', cors='*') - def print_receipt(self, receipt): + def scanner(self, receipt): print 'scanner' time.sleep(10) return '' diff --git a/addons/hw_scale/__openerp__.py b/addons/hw_scale/__openerp__.py index 6c83a854730..387a7e618d9 100644 --- a/addons/hw_scale/__openerp__.py +++ b/addons/hw_scale/__openerp__.py @@ -37,6 +37,7 @@ such as the Mettler Toledo Ariva. """, 'author': 'OpenERP SA', 'depends': ['hw_proxy'], + 'external_dependencies': {'python': ['serial']}, 'test': [ ], 'installable': True, diff --git a/addons/hw_scale/controllers/main.py b/addons/hw_scale/controllers/main.py index 531f2739f55..44a031f5566 100644 --- a/addons/hw_scale/controllers/main.py +++ b/addons/hw_scale/controllers/main.py @@ -178,28 +178,34 @@ class Scale(Thread): if not self.device: time.sleep(5) -s = Scale() - -hw_proxy.drivers['scale'] = s +scale_thread = None +if serial: + scale_thread = Scale() + hw_proxy.drivers['scale'] = scale_thread class ScaleDriver(hw_proxy.Proxy): @http.route('/hw_proxy/scale_read/', type='json', auth='none', cors='*') def scale_read(self): - return {'weight':s.get_weight(), 'unit':'kg', 'info':s.get_weight_info()} + if scale_thread: + return {'weight': scale_thread.get_weight(), 'unit':'kg', 'info': scale_thread.get_weight_info()} + return None @http.route('/hw_proxy/scale_zero/', type='json', auth='none', cors='*') def scale_zero(self): - s.set_zero() + if scale_thread: + scale_thread.set_zero() return True @http.route('/hw_proxy/scale_tare/', type='json', auth='none', cors='*') def scale_tare(self): - s.set_tare() + if scale_thread: + scale_thread.set_tare() return True @http.route('/hw_proxy/scale_clear_tare/', type='json', auth='none', cors='*') def scale_clear_tare(self): - s.clear_tare() + if scale_thread: + scale_thread.clear_tare() return True diff --git a/addons/hw_scanner/__openerp__.py b/addons/hw_scanner/__openerp__.py index aa74b0fbc5b..f38fa7dc420 100644 --- a/addons/hw_scanner/__openerp__.py +++ b/addons/hw_scanner/__openerp__.py @@ -38,6 +38,7 @@ point of sale module. """, 'author': 'OpenERP SA', 'depends': ['hw_proxy'], + 'external_dependencies': {'python': ['evdev']}, 'test': [ ], 'installable': True, diff --git a/addons/hw_scanner/controllers/main.py b/addons/hw_scanner/controllers/main.py index 057674c1ca1..0ae08d7d36b 100644 --- a/addons/hw_scanner/controllers/main.py +++ b/addons/hw_scanner/controllers/main.py @@ -111,8 +111,6 @@ class Scanner(Thread): def get_device(self): try: - if not evdev: - return None devices = [ device for device in listdir(self.input_dir)] keyboards = [ device for device in devices if ('kbd' in device) and ('keyboard' not in device.lower())] scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower())] @@ -135,7 +133,6 @@ class Scanner(Thread): been returned before. This is necessary to catch barcodes scanned while the POS is busy reading another barcode """ - self.lockedstart() while True: @@ -204,12 +201,12 @@ class Scanner(Thread): except Exception as e: self.set_status('error',str(e)) -s = Scanner() - -hw_proxy.drivers['scanner'] = s +scanner_thread = None +if evdev: + scanner_thread = Scanner() + hw_proxy.drivers['scanner'] = scanner_thread class ScannerDriver(hw_proxy.Proxy): @http.route('/hw_proxy/scanner', type='json', auth='none', cors='*') - def scanner(self): - return s.get_barcode() - + def scanner_thread(self): + return scanner_thread.get_barcode() if scanner_thread else None diff --git a/addons/im_chat/static/src/xml/im_chat.xml b/addons/im_chat/static/src/xml/im_chat.xml index 290de6b8cc6..6393064daa2 100644 --- a/addons/im_chat/static/src/xml/im_chat.xml +++ b/addons/im_chat/static/src/xml/im_chat.xml @@ -82,7 +82,7 @@ <div class="oe_im"> <div class="oe_im_frame_header"> <i class="fa fa-search oe_im_search_icon"/> - <input class="oe_im_searchbox" t-att-placeholder="_t('Search users...')"/> + <input class="oe_im_searchbox" placeholder="Search users..."/> </div> <div class="oe_im_users"></div> <div class="oe_im_content"></div> diff --git a/addons/mass_mailing/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml index d8db956f113..825d5a7d2db 100644 --- a/addons/mass_mailing/views/mass_mailing.xml +++ b/addons/mass_mailing/views/mass_mailing.xml @@ -270,7 +270,7 @@ </record> <record model="ir.actions.act_window" id="action_view_mass_mailing_lists"> - <field name="name">Contact Lists</field> + <field name="name">Mailing Lists</field> <field name="res_model">mail.mass_mailing.list</field> <field name="view_type">form</field> <field name="view_mode">tree,form</field> diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 73d3fc1660b..fbcee18d3e4 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -25,7 +25,7 @@ from collections import OrderedDict import openerp.addons.decimal_precision as dp from openerp.osv import fields, osv from openerp.tools import DEFAULT_SERVER_DATE_FORMAT -from openerp.tools import float_compare +from openerp.tools import float_compare, float_is_zero from openerp.tools.translate import _ from openerp import tools, SUPERUSER_ID from openerp.addons.product import _common @@ -604,7 +604,7 @@ class mrp_production(osv.osv): 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'product_qty': lambda *a: 1.0, 'user_id': lambda self, cr, uid, c: uid, - 'name': lambda x, y, z, c: x.pool.get('ir.sequence').next_by_code(y, z, 'mrp.production') or '/', + 'name': lambda self, cr, uid, context: self.pool['ir.sequence'].next_by_code(cr, uid, 'mrp.production', context=context) or '/', 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', context=c), 'location_src_id': _src_id_default, 'location_dest_id': _dest_id_default @@ -949,6 +949,7 @@ class mrp_production(osv.osv): uom_obj = self.pool.get("product.uom") production = self.browse(cr, uid, production_id, context=context) production_qty_uom = uom_obj._compute_qty(cr, uid, production.product_uom.id, production_qty, production.product_id.uom_id.id) + precision = self.pool['decimal.precision'].precision_get(cr, uid, 'Product Unit of Measure') main_production_move = False if production_mode == 'consume_produce': @@ -970,7 +971,8 @@ class mrp_production(osv.osv): 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) remaining_qty = subproduct_factor * production_qty_uom - qty - if remaining_qty: # In case you need to make more than planned + if not float_is_zero(remaining_qty, precision_rounding=precision): + # In case you need to make more than planned #consumed more in wizard than previously planned extra_move_id = stock_mov_obj.copy(cr, uid, produce_product.id, default={'product_uom_qty': remaining_qty, 'production_id': production_id}, context=context) @@ -1000,7 +1002,7 @@ class mrp_production(osv.osv): stock_mov_obj.action_consume(cr, uid, [raw_material_line.id], consumed_qty, raw_material_line.location_id.id, restrict_lot_id=consume['lot_id'], consumed_for=main_production_move, context=context) remaining_qty -= consumed_qty - if remaining_qty: + if not float_is_zero(remaining_qty, precision_rounding=precision): #consumed more in wizard than previously planned product = self.pool.get('product.product').browse(cr, uid, consume['product_id'], context=context) extra_move_id = self._make_consume_line_from_data(cr, uid, production, product, product.uom_id.id, remaining_qty, False, 0, context=context) diff --git a/addons/mrp/procurement.py b/addons/mrp/procurement.py index 7969a7f724a..8eb61d6c5cc 100644 --- a/addons/mrp/procurement.py +++ b/addons/mrp/procurement.py @@ -117,7 +117,7 @@ class procurement_order(osv.osv): if self.check_bom_exists(cr, uid, [procurement.id], context=context): #create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example) vals = self._prepare_mo_vals(cr, uid, procurement, context=context) - produce_id = production_obj.create(cr, SUPERUSER_ID, vals, context=context) + produce_id = production_obj.create(cr, SUPERUSER_ID, vals, context=dict(context, force_company=procurement.company_id.id)) res[procurement.id] = produce_id self.write(cr, uid, [procurement.id], {'production_id': produce_id}) self.production_order_create_note(cr, uid, procurement, context=context) diff --git a/addons/payment_adyen/models/adyen.py b/addons/payment_adyen/models/adyen.py index 7a995809222..52336cb48d9 100644 --- a/addons/payment_adyen/models/adyen.py +++ b/addons/payment_adyen/models/adyen.py @@ -28,7 +28,7 @@ class AcquirerAdyen(osv.Model): - yhpp: hosted payment page: pay.shtml for single, select.shtml for multiple """ return { - 'adyen_form_url': 'https://%s.adyen.com/hpp/pay.shtml' % environment, + 'adyen_form_url': 'https://%s.adyen.com/hpp/pay.shtml' % 'live' if environment == 'prod' else environment, } def _get_providers(self, cr, uid, context=None): diff --git a/addons/point_of_sale/report/pos_details.py b/addons/point_of_sale/report/pos_details.py index fe57b0042a8..bb784b98a45 100644 --- a/addons/point_of_sale/report/pos_details.py +++ b/addons/point_of_sale/report/pos_details.py @@ -129,13 +129,7 @@ class pos_details(report_sxw.rml_parse): return {} def _total_of_the_day(self, objects): - if self.total: - if self.total == self.total_invoiced: - return self.total - else: - return ((self.total or 0.00) - (self.total_invoiced or 0.00)) - else: - return False + return self.total or 0.00 def _sum_invoice(self, objects): return reduce(lambda acc, obj: diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index 2729d7118e9..83c5a7be2f5 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -170,7 +170,7 @@ openerp.point_of_sale.load_db = function load_db(instance, module){ for (var i = 0; i < packagings.length; i++) { str += '|' + packagings[i].barcode; } - str = product.id + ':' + str.replace(':','') + '\n'; + str = product.id + ':' + str.replace(/:/g,'') + '\n'; return str; }, add_products: function(products){ diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 1b149a08069..fa9c4d5ce8d 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -1196,18 +1196,19 @@ class procurement_order(osv.osv): uom_obj = self.pool.get("product.uom") for procurement in self.browse(cr, uid, ids, context=context): if procurement.rule_id.action == 'buy' and procurement.purchase_line_id: - uom = procurement.purchase_line_id.product_uom - product_qty = uom_obj._compute_qty_obj(cr, uid, procurement.product_uom, procurement.product_qty, uom, context=context) if procurement.purchase_line_id.state not in ('draft', 'cancel'): raise UserError( _('Can not cancel this procurement like this as the related purchase order has been confirmed already. Please cancel the purchase order first. ')) - if float_compare(procurement.purchase_line_id.product_qty, product_qty, 0, precision_rounding=uom.rounding) > 0: - purchase_line_obj.write(cr, uid, [procurement.purchase_line_id.id], {'product_qty': procurement.purchase_line_id.product_qty - product_qty}, context=context) - else: + + new_qty, new_price = self._calc_new_qty_price(cr, uid, procurement, cancel=True, context=context) + if new_qty != procurement.purchase_line_id.product_qty: + purchase_line_obj.write(cr, uid, [procurement.purchase_line_id.id], {'product_qty': new_qty, 'price_unit': new_price}, context=context) + if float_compare(new_qty, 0.0, precision_rounding=procurement.product_uom.rounding) != 1: if procurement.purchase_line_id.id not in lines_to_cancel: lines_to_cancel += [procurement.purchase_line_id.id] if lines_to_cancel: purchase_line_obj.action_cancel(cr, uid, lines_to_cancel, context=context) + purchase_line_obj.unlink(cr, uid, lines_to_cancel, context=context) return super(procurement_order, self).propagate_cancels(cr, uid, ids, context=context) def _run(self, cr, uid, procurement, context=None): @@ -1371,6 +1372,41 @@ class procurement_order(osv.osv): res[procurement.id] = values return res + def _calc_new_qty_price(self, cr, uid, procurement, po_line=None, cancel=False, context=None): + if not po_line: + po_line = procurement.purchase_line_id + + uom_obj = self.pool.get('product.uom') + qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, + procurement.product_id.uom_po_id.id) + if cancel: + qty = -qty + + # Make sure we use the minimum quantity of the partner corresponding to the PO + if po_line.product_id.seller_id.id == po_line.order_id.partner_id.id: + supplierinfo_min_qty = po_line.product_id.seller_qty + else: + supplierinfo_obj = self.pool.get('product.supplierinfo') + supplierinfo_ids = supplierinfo_obj.search(cr, uid, [('name', '=', po_line.order_id.partner_id.id), ('product_tmpl_id', '=', po_line.product_id.product_tmpl_id.id)]) + supplierinfo_min_qty = supplierinfo_obj.browse(cr, uid, supplierinfo_ids).min_qty + + if supplierinfo_min_qty == 0.0: + qty += po_line.product_qty + else: + # Recompute quantity by adding existing running procurements. + for proc in po_line.procurement_ids: + qty += uom_obj._compute_qty(cr, uid, proc.product_uom.id, proc.product_qty, + proc.product_id.uom_po_id.id) if proc.state == 'running' else 0.0 + qty = max(qty, supplierinfo_min_qty) if qty > 0.0 else 0.0 + + price = po_line.price_unit + if qty != po_line.product_qty: + pricelist_obj = self.pool.get('product.pricelist') + pricelist_id = po_line.order_id.partner_id.property_product_pricelist_purchase.id + price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, po_line.order_id.partner_id.id, {'uom': procurement.product_uom.id})[pricelist_id] + + return qty, price + def _get_grouping_dicts(self, cr, uid, ids, context=None): """ It will group the procurements according to the pos they should go into. That way, lines going to the same @@ -1455,8 +1491,11 @@ class procurement_order(osv.osv): for proc in procurements: if po_prod_dict.get(proc.product_id.id): po_line = po_prod_dict[proc.product_id.id] - uom_id = po_line.product_uom #Convert to UoM of existing line + # FIXME: compute quantity using `_calc_new_qty_price` method. + # new_qty, new_price = self._calc_new_qty_price(cr, uid, proc, po_line=po_line, context=context) + uom_id = po_line.product_uom # Convert to UoM of existing line qty = uom_obj._compute_qty_obj(cr, uid, proc.product_uom, proc.product_qty, uom_id) + if lines_to_update.get(po_line): lines_to_update[po_line] += [(proc.id, qty)] else: diff --git a/addons/sale/test/manual_order_policy.yml b/addons/sale/test/manual_order_policy.yml index d2a1ff0195f..1bb1f1d4415 100644 --- a/addons/sale/test/manual_order_policy.yml +++ b/addons/sale/test/manual_order_policy.yml @@ -7,7 +7,7 @@ - !python {model: sale.order}: | sale_order = self.browse(cr, uid, ref("sale_order_2")) - assert len(sale_order.invoice_ids) == False, "Invoice should not created." + assert len(sale_order.invoice_ids) == 0, "Invoice should not created." - I create advance invoice where type is 'Fixed Price'. - diff --git a/addons/sale_layout/security/ir.model.access.csv b/addons/sale_layout/security/ir.model.access.csv index 10a5f7b06b0..9ec03a6e378 100644 --- a/addons/sale_layout/security/ir.model.access.csv +++ b/addons/sale_layout/security/ir.model.access.csv @@ -1,6 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink report_layout_category_1,report_layout_category_1,model_sale_layout_category,base.group_sale_manager,1,1,1,1 report_layout_category_2,report_layout_category_2,model_sale_layout_category,account.group_account_manager,1,1,1,1 -report_layout_category_3,report_layout_category_3,model_sale_layout_category,base.group_sale_salesman,1,1,1,O +report_layout_category_3,report_layout_category_3,model_sale_layout_category,base.group_sale_salesman,1,1,1,0 report_layout_category_4,report_layout_category_4,model_sale_layout_category,base.group_sale_salesman_all_leads,1,1,1,0 report_layout_category_5,report_layout_category_5,model_sale_layout_category,account.group_account_invoice,1,0,0,0 diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 13de717ce36..5fbfe6fedec 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1627,7 +1627,7 @@ class stock_production_lot(osv.osv): # Move # ---------------------------------------------------- -class stock_move(models.Model): +class stock_move(osv.osv): _name = "stock.move" _description = "Stock Move" _order = 'picking_id, sequence, id' @@ -1648,11 +1648,12 @@ class stock_move(models.Model): res.append((line.id, name)) return res - @api.depends('product_id', 'product_uom_qty', 'product_uom', 'product_id.uom_id') - def _quantity_normalize(self): + def _quantity_normalize(self, cr, uid, ids, name, args, context=None): uom_obj = self.pool.get('product.uom') - for m in self: - m.product_qty = uom_obj._compute_qty_obj(self.env.cr, self.env.uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id, context=self.env.context) + res = {} + for m in self.browse(cr, uid, ids, context=context): + res[m.id] = uom_obj._compute_qty_obj(cr, uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id, context=context) + return res def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None): uom_obj = self.pool.get('product.uom') @@ -1736,6 +1737,11 @@ class stock_move(models.Model): res += [x.id for x in picking.move_lines] return res + def _get_moves_from_prod(self, cr, uid, ids, context=None): + if ids: + return self.pool.get('stock.move').search(cr, uid, [('product_id', 'in', ids)], context=context) + return [] + def _set_product_qty(self, cr, uid, id, field, value, arg, context=None): """ The meaning of product_qty field changed lately and is now a functional field computing the quantity in the default product UoM. This code has been added to raise an error if a write is made given a value @@ -1744,9 +1750,6 @@ class stock_move(models.Model): """ raise UserError(_('The requested operation cannot be processed because of a programming error setting the `product_qty` field instead of the `product_uom_qty`.')) - product_qty = new_fields.Float(compute='_quantity_normalize', inverse='_set_product_qty', digits=0, store=True, string='Quantity', - help='Quantity in the default UoM of the product') - _columns = { 'sequence': fields.integer('Sequence'), 'name': fields.char('Description', required=True, select=True), @@ -1755,6 +1758,8 @@ class stock_move(models.Model): 'date': fields.datetime('Date', required=True, select=True, help="Move date: scheduled date until move is done, then date of actual move processing", states={'done': [('readonly', True)]}), 'date_expected': fields.datetime('Expected Date', states={'done': [('readonly', True)]}, required=True, select=True, help="Scheduled date for the processing of this move"), 'product_id': fields.many2one('product.product', 'Product', required=True, select=True, domain=[('type', '<>', 'service')], states={'done': [('readonly', True)]}), + 'product_qty': fields.function(_quantity_normalize, fnct_inv=_set_product_qty, type='float', digits=0, store=True, string='Quantity', + help='Quantity in the default UoM of the product'), 'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, states={'done': [('readonly', True)]}, help="This is the quantity of products from an inventory " @@ -2759,6 +2764,36 @@ class stock_inventory(osv.osv): vals.append(product_line) return vals + def _check_filter_product(self, cr, uid, ids, context=None): + for inventory in self.browse(cr, uid, ids, context=context): + if inventory.filter == 'none' and inventory.product_id and inventory.location_id and inventory.lot_id: + return True + if inventory.filter not in ('product', 'product_owner') and inventory.product_id: + return False + if inventory.filter != 'lot' and inventory.lot_id: + return False + if inventory.filter not in ('owner', 'product_owner') and inventory.partner_id: + return False + if inventory.filter != 'pack' and inventory.package_id: + return False + return True + + def onchange_filter(self, cr, uid, ids, filter, context=None): + to_clean = { 'value': {} } + if filter not in ('product', 'product_owner'): + to_clean['value']['product_id'] = False + if filter != 'lot': + to_clean['value']['lot_id'] = False + if filter not in ('owner', 'product_owner'): + to_clean['value']['partner_id'] = False + if filter != 'pack': + to_clean['value']['package_id'] = False + return to_clean + + _constraints = [ + (_check_filter_product, 'The selected inventory options are not coherent.', + ['filter', 'product_id', 'lot_id', 'partner_id', 'package_id']), + ] class stock_inventory_line(osv.osv): _name = "stock.inventory.line" diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 9d751828373..36de197aff8 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -107,7 +107,7 @@ <group> <group> <field name="location_id" domain="[('usage','not in', ['supplier','production'])]"/> - <field name="filter" string="Inventory of" widget='radio' attrs="{'readonly': [('state', '!=', 'draft')]}"/> + <field name="filter" string="Inventory of" widget='radio' attrs="{'readonly': [('state', '!=', 'draft')]}" on_change="onchange_filter(filter)"/> </group> <group> <field name="date"/> diff --git a/addons/stock/test/inventory.yml b/addons/stock/test/inventory.yml index a0b0f58512b..4bfc70d3dd1 100644 --- a/addons/stock/test/inventory.yml +++ b/addons/stock/test/inventory.yml @@ -9,6 +9,7 @@ - !record {model: stock.inventory, id: inventory_test0}: name: Test + filter: product product_id: inventory_product - I confirm the inventory and check that my inventory has no line, as the product is a new one. @@ -60,6 +61,7 @@ - !record {model: stock.inventory, id: inventory_test1}: name: second test inventory + filter: product product_id: inventory_product - I confirm the inventory and check that my inventory has one line, with a quantity of 10. diff --git a/addons/stock/wizard/stock_change_product_qty.py b/addons/stock/wizard/stock_change_product_qty.py index d541338ed2e..435cf85c82b 100644 --- a/addons/stock/wizard/stock_change_product_qty.py +++ b/addons/stock/wizard/stock_change_product_qty.py @@ -94,8 +94,15 @@ class stock_change_product_qty(osv.osv_memory): ctx = context.copy() ctx['location'] = data.location_id.id ctx['lot_id'] = data.lot_id.id + if data.product_id.id and data.lot_id.id: + filter = 'none' + elif data.product_id.id: + filter = 'product' + else: + filter = 'none' inventory_id = inventory_obj.create(cr, uid, { 'name': _('INV: %s') % tools.ustr(data.product_id.name), + 'filter': filter, 'product_id': data.product_id.id, 'location_id': data.location_id.id, 'lot_id': data.lot_id.id}, context=context) diff --git a/addons/stock_account/stock.py b/addons/stock_account/stock.py index 392bc3ff2f3..252ef04c051 100644 --- a/addons/stock_account/stock.py +++ b/addons/stock_account/stock.py @@ -151,6 +151,13 @@ class stock_move(osv.osv): if move.product_uos: uos_id = move.product_uos.id quantity = move.product_uos_qty + + taxes_ids = [] + if move.origin_returned_move_id.purchase_line_id.taxes_id: + taxes_ids = [tax.id for tax in move.origin_returned_move_id.purchase_line_id.taxes_id] + elif move.procurement_id.sale_line_id.tax_id: + taxes_ids = [tax.id for tax in move.procurement_id.sale_line_id.tax_id] + return { 'name': move.name, 'account_id': account_id, @@ -158,6 +165,7 @@ class stock_move(osv.osv): 'uos_id': uos_id, 'quantity': quantity, 'price_unit': self._get_price_unit_invoice(cr, uid, move, inv_type), + 'invoice_line_tax_id': [(6, 0, taxes_ids)], 'discount': 0.0, 'account_analytic_id': False, } @@ -292,6 +300,7 @@ class stock_picking(osv.osv): move_obj = self.pool.get('stock.move') invoices = {} is_extra_move, extra_move_tax = move_obj._get_moves_taxes(cr, uid, moves, context=context) + product_price_unit = {} for move in moves: company = move.company_id origin = move.picking_id.name @@ -317,6 +326,10 @@ class stock_picking(osv.osv): invoice_line_vals = move_obj._get_invoice_line_vals(cr, uid, move, partner, inv_type, context=context) invoice_line_vals['invoice_id'] = invoices[key] invoice_line_vals['origin'] = origin + if not is_extra_move[move.id]: + product_price_unit[invoice_line_vals['product_id']] = invoice_line_vals['price_unit'] + if is_extra_move[move.id] and invoice_line_vals['product_id'] in product_price_unit: + invoice_line_vals['price_unit'] = product_price_unit[invoice_line_vals['product_id']] if is_extra_move[move.id] and extra_move_tax[move.picking_id, move.product_id]: invoice_line_vals['invoice_line_tax_id'] = extra_move_tax[move.picking_id, move.product_id] diff --git a/addons/stock_dropshipping/stock_dropshipping.py b/addons/stock_dropshipping/stock_dropshipping.py index cbd079263ca..2fe37ea8565 100644 --- a/addons/stock_dropshipping/stock_dropshipping.py +++ b/addons/stock_dropshipping/stock_dropshipping.py @@ -6,16 +6,17 @@ from openerp import models, api class sale_order_line(models.Model): _inherit = 'sale.order.line' - @api.one + @api.multi def _check_routing(self, product, warehouse): """ skip stock verification if the route goes from supplier to customer As the product never goes in stock, no need to verify it's availibility """ res = super(sale_order_line, self)._check_routing(product, warehouse) if not res: - for pull_rule in self.route_id.pull_ids: - if (pull_rule.picking_type_id.default_location_src_id.usage == 'supplier' and - pull_rule.picking_type_id.default_location_dest_id.usage == 'customer'): - res = True - break + for line in self: + for pull_rule in line.route_id.pull_ids: + if (pull_rule.picking_type_id.default_location_src_id.usage == 'supplier' and + pull_rule.picking_type_id.default_location_dest_id.usage == 'customer'): + res = True + break return res diff --git a/addons/stock_dropshipping/test/crossdock.yml b/addons/stock_dropshipping/test/crossdock.yml index 5ad1f1db187..2c79a1a3754 100644 --- a/addons/stock_dropshipping/test/crossdock.yml +++ b/addons/stock_dropshipping/test/crossdock.yml @@ -1,42 +1,58 @@ -- - Create new product without any routes -- - !record {model: product.product, id: cross_shop_product}: - name: PCE - type: product - categ_id: product.product_category_1 - list_price: 100.0 - standard_price: 70.0 - seller_ids: - - delay: 1 - name: base.res_partner_2 - min_qty: 2.0 - qty: 5.0 - type: product - uom_id: product.product_uom_unit - uom_po_id: product.product_uom_unit -- - Create a sales order with a line of 100 PCE incoming shipment, with route_id crossdock shipping. -- - !record {model: sale.order, id: sale_order_crossdock_shpng}: - partner_id: base.res_partner_4 - note: Create Sales order - order_line: - - product_id: cross_shop_product - product_uom_qty: 100.00 +- + Create a supplier. +- + !record {model: res.partner, id: supplier_crossdock}: + name: Crossdocking supplier +- + Use the warehouse created in cancellation_propagated.yml. +- + !python {model: stock.warehouse, id: wh_pps}: | + assert self.crossdock_route_id.active, 'Crossdock route is not active' +- + Create new product without any routes. +- + !record {model: product.product, id: cross_shop_product}: + name: PCE + type: product + categ_id: product.product_category_1 + list_price: 100.0 + standard_price: 70.0 + seller_ids: + - delay: 1 + name: supplier_crossdock + min_qty: 2.0 + qty: 5.0 + type: product + uom_id: product.product_uom_unit + uom_po_id: product.product_uom_unit +- + Create a sales order with a line of 100 PCE incoming shipment, with route_id crossdock shipping. +- + !record {model: sale.order, id: sale_order_crossdock_shpng}: + partner_id: base.res_partner_4 + warehouse_id: wh_pps + note: Create Sales order + order_line: + - product_id: cross_shop_product + product_uom_qty: 100.00 - !python {model: sale.order.line}: | - route_warehouse0_crossdock = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock.warehouse0')).crossdock_route_id.id + route_wh_pps_crossdock = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock_dropshipping.wh_pps')).crossdock_route_id.id order = self.pool.get('sale.order').browse(cr, uid, ref('sale_order_crossdock_shpng')) line_ids = [x.id for x in order.order_line] - self.write(cr, uid, line_ids, {'route_id': route_warehouse0_crossdock}) -- - Confirm sales order -- - !workflow {model: sale.order, action: order_confirm, ref: sale_order_crossdock_shpng} -- - Check a quotation was created to a certain supplier and confirm so it becomes a confirmed purchase order + self.write(cr, uid, line_ids, {'route_id': route_wh_pps_crossdock}) +- + Confirm sales order. +- + !workflow {model: sale.order, action: order_confirm, ref: sale_order_crossdock_shpng} +- + Run the scheduler. +- + !python {model: procurement.order}: | + self.run_scheduler(cr, uid) +- + Check a quotation was created for the created supplier and confirm it. - !python {model: purchase.order}: | - po_id = self.search(cr, uid, [('partner_id', '=', ref('base.res_partner_2'))]) + po_id = self.search(cr, uid, [('partner_id', '=', ref('supplier_crossdock')), ('state','=','draft')]) self.wkf_confirm_order(cr, uid, po_id) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 893caf3040d..663a6adde09 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -451,7 +451,7 @@ def content_disposition(filename): if browser == 'msie' and version < 9: return "attachment; filename=%s" % escaped elif browser == 'safari': - return "attachment; filename=%s" % filename + return "attachment; filename=\"%s\"" % filename else: return "attachment; filename*=UTF-8''%s" % escaped diff --git a/addons/website_membership/__init__.py b/addons/website_membership/__init__.py index ee5959455ad..9f86759e32b 100644 --- a/addons/website_membership/__init__.py +++ b/addons/website_membership/__init__.py @@ -1 +1,2 @@ import controllers +import models diff --git a/addons/website_membership/controllers/main.py b/addons/website_membership/controllers/main.py index beedd50a04f..1df80c36c71 100644 --- a/addons/website_membership/controllers/main.py +++ b/addons/website_membership/controllers/main.py @@ -27,7 +27,7 @@ class WebsiteMembership(http.Controller): '/members/association/<membership_id>/country/<country_name>-<int:country_id>/page/<int:page>', '/members/association/<membership_id>/country/<int:country_id>/page/<int:page>', ], type='http', auth="public", website=True) - def members(self, membership_id=None, country_name=None, country_id=0, page=0, **post): + def members(self, membership_id=None, country_name=None, country_id=0, page=1, **post): cr, uid, context = request.cr, request.uid, request.context product_obj = request.registry['product.product'] country_obj = request.registry['res.country'] @@ -56,7 +56,7 @@ class WebsiteMembership(http.Controller): membership_line_ids = [] country_domain = ('free_member', '=', True) countries = partner_obj.read_group( - cr, uid, [country_domain, ("website_published", "=", True)], ["id", "country_id"], + cr, SUPERUSER_ID, [country_domain, ("website_published", "=", True)], ["id", "country_id"], groupby="country_id", orderby="country_id", context=request.context) countries_total = sum(country_dict['country_id_count'] for country_dict in countries) @@ -64,11 +64,12 @@ class WebsiteMembership(http.Controller): if country_id: line_domain.append(('partner.country_id', '=', country_id)) current_country = country_obj.read(cr, uid, country_id, ['id', 'name'], context) - if not any(x['country_id'][0] == country_id for x in countries): + if not any(x['country_id'][0] == country_id for x in countries if x['country_id']): countries.append({ 'country_id_count': 0, 'country_id': (country_id, current_country["name"]) }) + countries = filter(lambda d:d['country_id'], countries) countries.sort(key=lambda d: d['country_id'][1]) countries.insert(0, { @@ -82,15 +83,32 @@ class WebsiteMembership(http.Controller): # make sure we don't access to lines with unpublished membershipts line_domain.append(('membership_id', 'in', membership_ids)) - # displayed membership lines + limit = self._references_per_page + offset = limit * (page - 1) + + count_members = 0 + membership_line_ids = [] + # displayed non-free membership lines if membership_id != 'free': - membership_line_ids = membership_line_obj.search(cr, SUPERUSER_ID, line_domain, context=context) + count_members = membership_line_obj.search_count(cr, SUPERUSER_ID, line_domain, context=context) + if offset <= count_members: + membership_line_ids = tuple(membership_line_obj.search(cr, SUPERUSER_ID, line_domain, offset, limit, context=context)) membership_lines = membership_line_obj.browse(cr, uid, membership_line_ids, context=context) # TODO: Following line can be deleted in master. Kept for retrocompatibility. membership_lines = sorted(membership_lines, key=lambda x: x.membership_id.website_sequence) - partner_ids = [m.partner.id for m in membership_lines] - free_partner_ids = partner_obj.search(cr, SUPERUSER_ID, [('free_member', '=', True), ('website_published', '=', True)], context=context) - google_map_partner_ids = ",".join(map(str, partner_ids)) + page_partner_ids = set(m.partner.id for m in membership_lines) + + google_map_partner_ids = [] + if request.env.ref('website_membership.opt_index_google_map').customize_show: + membership_lines_ids = membership_line_obj.search(cr, uid, line_domain, context=context) + google_map_partner_ids = membership_line_obj.get_published_companies(cr, uid, membership_line_ids, limit=2000, context=context) + + search_domain = [('free_member', '=', True), ('website_published', '=', True)] + if post_name: + search_domain += ['|', ('name', 'ilike', post_name), ('website_description', 'ilike', post_name)] + if country_id: + search_domain += [('country_id', '=', country_id)] + free_partner_ids = partner_obj.search(cr, SUPERUSER_ID, search_domain, context=context) memberships_data = [] for membership_record in memberships: memberships_data.append({'id': membership_record.id, 'name': membership_record.name}) @@ -100,12 +118,23 @@ class WebsiteMembership(http.Controller): if free_partner_ids: memberships_data.append({'id': 'free', 'name': _('Free Members')}) if not membership_id or membership_id == 'free': - memberships_partner_ids['free'] = free_partner_ids + if count_members < offset + limit: + free_start = max(offset - count_members, 0) + free_end = max(offset + limit - count_members, 0) + memberships_partner_ids['free'] = free_partner_ids[free_start:free_end] + page_partner_ids |= set(memberships_partner_ids['free']) + google_map_partner_ids += free_partner_ids[:2000-len(google_map_partner_ids)] + count_members += len(free_partner_ids) + + google_map_partner_ids = ",".join(map(str, google_map_partner_ids)) + + partners = { p.id: p for p in partner_obj.browse(request.cr, SUPERUSER_ID, list(page_partner_ids), request.context)} - partners = dict((p.id, p) for p in partner_obj.browse(request.cr, SUPERUSER_ID, list(set(partner_ids + free_partner_ids)), request.context)) + base_url = '/members%s%s' % ('/association/%s' % membership_id if membership_id else '', + '/country/%s' % country_id if country_id else '') # request pager for lines - pager = request.website.pager(url="/members", total=len(membership_line_ids), page=page, step=self._references_per_page, scope=7, url_args=post) + pager = request.website.pager(url=base_url, total=count_members, page=page, step=limit, scope=7, url_args=post) values = { 'partners': partners, diff --git a/addons/website_membership/models/__init__.py b/addons/website_membership/models/__init__.py new file mode 100644 index 00000000000..58702d876e4 --- /dev/null +++ b/addons/website_membership/models/__init__.py @@ -0,0 +1 @@ +import membership diff --git a/addons/website_membership/models/membership.py b/addons/website_membership/models/membership.py new file mode 100644 index 00000000000..feba5ce6ea6 --- /dev/null +++ b/addons/website_membership/models/membership.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################## + +from openerp.osv import osv, fields + +class membership_membership_line(osv.Model): + _inherit = 'membership.membership_line' + + def get_published_companies(self, cr, uid, ids, limit=None, context=None): + if not ids: + return [] + limit_clause = '' if limit is None else ' LIMIT %d' % limit + cr.execute('SELECT DISTINCT p.id \ + FROM res_partner p INNER JOIN membership_membership_line m \ + ON p.id = m.partner \ + WHERE website_published AND is_company AND m.id IN %s ' + limit_clause, (tuple(ids),)) + return [partner_id[0] for partner_id in cr.fetchall()] diff --git a/addons/website_quote/models/order.py b/addons/website_quote/models/order.py index 9090d68a68e..9be35a29fb1 100644 --- a/addons/website_quote/models/order.py +++ b/addons/website_quote/models/order.py @@ -279,6 +279,8 @@ class sale_quote_option(osv.osv): 'name': product_obj.name, 'uom_id': product_obj.product_tmpl_id.uom_id.id, }) + if product_obj.description_sale: + vals['name'] += '\n'+product_obj.description_sale return {'value': vals} class sale_order_option(osv.osv): @@ -311,6 +313,8 @@ class sale_order_option(osv.osv): 'name': product_obj.name, 'uom_id': product_obj.product_tmpl_id.uom_id.id, }) + if product_obj.description_sale: + vals['name'] += '\n'+product_obj.description_sale return {'value': vals} class product_template(osv.Model): diff --git a/addons/website_quote/static/src/css/website_quotation.css b/addons/website_quote/static/src/css/website_quotation.css index c4bbd4d9c64..defa37feef5 100644 --- a/addons/website_quote/static/src/css/website_quotation.css +++ b/addons/website_quote/static/src/css/website_quotation.css @@ -42,6 +42,11 @@ padding-left: 30px; font-size: 90%; } +@media (max-width: 991px) { + .o_website_quote .bs-sidebar { + position: static; + } +} @media (min-width: 992px) { .o_website_quote .bs-sidebar .nav > .active > ul { display: block; diff --git a/addons/website_quote/views/website_quotation.xml b/addons/website_quote/views/website_quotation.xml index c53df0bdd56..55b90835578 100644 --- a/addons/website_quote/views/website_quotation.xml +++ b/addons/website_quote/views/website_quotation.xml @@ -157,7 +157,7 @@ <template id="quotation_toolbar"> <div class="text-center hidden-print" t-if="quotation.state in ('draft', 'sent', 'waiting_date')"> - <a class="btn btn-success" data-toggle="modal" data-target="#modalaccept" + <a class="btn btn-success" data-toggle="modal" data-target="#modelaccept" t-if="days_valid >= 0"> <i class="fa fa-check"></i> Accept </a> diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 59dcbeb0dda..76d2e353e04 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -980,10 +980,13 @@ class website_sale(http.Controller): 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): + def get_unit_price(self, product_ids, add_qty, use_order_pricelist=False, **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 + if use_order_pricelist: + pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id + else: + pricelist_id = 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} diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js index 111a3c70ea4..97f3f3a7ac0 100644 --- a/addons/website_sale/static/src/js/website_sale.js +++ b/addons/website_sale/static/src/js/website_sale.js @@ -56,7 +56,8 @@ $('.oe_website_sale').each(function () { if (isNaN(value)) value = 0; openerp.jsonRpc("/shop/get_unit_price", 'call', { 'product_ids': product_ids, - 'add_qty': value}) + 'add_qty': value, + 'use_order_pricelist': true}) .then(function (res) { //basic case $dom.find('span.oe_currency_value').last().text(res[product_id].toFixed(2)); |
