From ba070781b897a856e2f1d6cc9f735ee5da394e9d Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 29 Jun 2016 17:09:51 +0200 Subject: [PATCH] [FIX] point_of_sale: restored removed report The sale of the day report was removed during new api migration as it was thought, it could be replaced by pivot view. Due to the lack of some options and the problems to be printed, create similar report. This commit adds a report in the PoS interface to be printed on ESC/POS printers and add a JS wizard to restore it in the backend with date selection. The wizard is made in a new web widget because adding a wizard requires changes not compatible with stable version. Creating a proper wizard should be done in v10 opw-681833 --- addons/point_of_sale/__openerp__.py | 1 + addons/point_of_sale/controllers/main.py | 7 ++ addons/point_of_sale/models/pos_order.py | 104 +++++++++++++++- addons/point_of_sale/static/src/js/chrome.js | 23 ++++ addons/point_of_sale/static/src/js/devices.js | 19 +++ addons/point_of_sale/static/src/js/report.js | 63 ++++++++++ addons/point_of_sale/static/src/xml/pos.xml | 116 ++++++++++++++++++ .../views/point_of_sale_report.xml | 6 + .../views/point_of_sale_template.xml | 2 + .../views/pos_order_report_view.xml | 6 + .../views/report_saledetails.xml | 84 +++++++++++++ 11 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 addons/point_of_sale/static/src/js/report.js create mode 100644 addons/point_of_sale/views/report_saledetails.xml diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index a42c2df9106..a4bd8fc247b 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -59,6 +59,7 @@ Main Features 'views/report_statement.xml', 'views/report_usersproduct.xml', 'views/report_userlabel.xml', + 'views/report_saledetails.xml', 'views/point_of_sale.xml', 'views/point_of_sale_dashboard.xml', ], diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py index cd4d91b56c9..305e743c3f4 100644 --- a/addons/point_of_sale/controllers/main.py +++ b/addons/point_of_sale/controllers/main.py @@ -18,3 +18,10 @@ class PosController(http.Controller): return werkzeug.utils.redirect('/web#action=point_of_sale.action_client_pos_menu') pos_sessions.login() return request.render('point_of_sale.index') + + @http.route('/pos/sale_details_report', type='http', auth='user') + def print_sale_details(self, date_start=False, date_stop=False, **kw): + r = request.env['report.point_of_sale.report_saledetails'] + pdf = request.env['report'].with_context(date_start=date_start, date_stop=date_stop).get_pdf(r, 'point_of_sale.report_saledetails') + pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))] + return request.make_response(pdf, headers=pdfhttpheaders) diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index a8b466f1acb..fb299dae32e 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import logging +from datetime import timedelta from functools import partial import psycopg2 @@ -8,7 +9,7 @@ import psycopg2 from odoo import api, fields, models, tools, _ from odoo.tools import float_is_zero from odoo.exceptions import UserError - +from odoo.http import request import odoo.addons.decimal_precision as dp _logger = logging.getLogger(__name__) @@ -733,3 +734,104 @@ class PosOrderLine(models.Model): def _get_tax_ids_after_fiscal_position(self): for line in self: line.tax_ids_after_fiscal_position = line.order_id.fiscal_position_id.map_tax(line.tax_ids) + + +class ReportSaleDetails(models.AbstractModel): + + _name = 'report.point_of_sale.report_saledetails' + + + @api.model + def get_sale_details(self, date_start=False, date_stop=False, company=False): + """ Serialise the orders of the day information + + params: date_start, date_stop string representing the day of order + """ + if not company: + company = self.env.user.company_id + + today = fields.Datetime.from_string(fields.Date.context_today(self)) + if date_start: + date_start = fields.Datetime.from_string(date_start) + else: + # start by default today 00:00:00 + date_start = today + + if date_stop: + # set time to 23:59:59 + date_stop = fields.Datetime.from_string(date_stop) + timedelta(days=1, seconds=-1) + else: + # stop by default today 23:59:59 + date_stop = today + timedelta(days=1, seconds=-1) + + # avoid a date_stop smaller than date_start + date_stop = max(date_stop, date_start) + + date_start = fields.Datetime.to_string(date_start) + date_stop = fields.Datetime.to_string(date_stop) + orders = self.env['pos.order'].search([('date_order', '>=', date_start), ('date_order', '<=', date_stop), + ('state', 'in', ['paid','invoiced','done']), + ('company_id', '=', company.id)]) + user_currency = self.env.user.company_id.currency_id + + total = 0.0 + products_sold = {} + taxes = {} + for order in orders: + if user_currency != order.pricelist_id.currency_id: + total += order.pricelist_id.currency_id.compute(order.amount_total, user_currency) + else: + total += order.amount_total + currency = order.session_id.currency_id + + for line in order.lines: + key = (line.product_id, line.price_unit, line.discount) + products_sold.setdefault(key, 0.0) + products_sold[key] += line.qty + + if line.tax_ids_after_fiscal_position: + line_taxes = line.tax_ids_after_fiscal_position.compute_all(line.price_unit * (1-(line.discount or 0.0)/100.0), currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False) + for tax in line_taxes['taxes']: + taxes.setdefault(tax['id'], {'name': tax['name'], 'total':0.0}) + taxes[tax['id']]['total'] += tax['amount'] + + st_line_ids = self.env["account.bank.statement.line"].search([('pos_statement_id', 'in', orders.ids)]).ids + if st_line_ids: + self.env.cr.execute(""" + SELECT aj.name, sum(amount) total + FROM account_bank_statement_line AS absl, + account_bank_statement AS abs, + account_journal AS aj + WHERE absl.statement_id = abs.id + AND abs.journal_id = aj.id + AND absl.id IN %s + GROUP BY aj.name + """, (tuple(st_line_ids),)) + payments = self.env.cr.dictfetchall() + else: + payments = [] + + return { + 'total_paid': user_currency.round(total), + 'payments': payments, + 'company_name': self.env.user.company_id.name, + 'taxes': taxes.values(), + 'products': [{ + 'product_id': product.id, + 'product_name': product.name[:20], + 'code': product.default_code, + 'quantity': qty, + 'price_unit': price_unit, + 'discount': discount, + 'uom': product.uom_id.name + } for (product, price_unit, discount), qty in products_sold.items()] + } + + @api.multi + def render_html(self, data=None): + company = request.env.user.company_id + date_start = self.env.context.get('date_start', False) + date_stop = self.env.context.get('date_stop', False) + data = dict(data or {}, date_start=date_start, date_stop=date_stop) + data.update(self.get_sale_details(date_start, date_stop, company)) + return self.env['report'].render('point_of_sale.report_saledetails', data) \ No newline at end of file diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js index 926fe23eee7..f7bf1f78010 100644 --- a/addons/point_of_sale/static/src/js/chrome.js +++ b/addons/point_of_sale/static/src/js/chrome.js @@ -406,6 +406,23 @@ var ProxyStatusWidget = StatusWidget.extend({ }, }); + +/* --------- The Sale Details --------- */ + +// Generates a report to print the sales of the +// day on a ticket + +var SaleDetailsButton = PosBaseWidget.extend({ + template: 'SaleDetailsButton', + start: function(){ + var self = this; + this.$el.click(function(){ + self.pos.proxy.print_sale_details(); + }); + }, +}); + + /*--------------------------------------*\ | THE CHROME | \*======================================*/ @@ -662,6 +679,11 @@ var Chrome = PosBaseWidget.extend({ 'name': 'order_selector', 'widget': OrderSelectorWidget, 'replace': '.placeholder-OrderSelectorWidget', + },{ + 'name': 'sale_details', + 'widget': SaleDetailsButton, + 'append': '.pos-rightheader', + 'condition': function(){ return this.pos.config.use_proxy; }, },{ 'name': 'proxy_status', 'widget': ProxyStatusWidget, @@ -769,6 +791,7 @@ return { HeaderButtonWidget: HeaderButtonWidget, OrderSelectorWidget: OrderSelectorWidget, ProxyStatusWidget: ProxyStatusWidget, + SaleDetailsButton: SaleDetailsButton, StatusWidget: StatusWidget, SynchNotificationWidget: SynchNotificationWidget, UsernameWidget: UsernameWidget, diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index f9dd33e0ead..cfb24bf39ed 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -2,8 +2,10 @@ odoo.define('point_of_sale.devices', function (require) { "use strict"; var core = require('web.core'); +var Model = require('web.DataModel'); var Session = require('web.Session'); +var QWeb = core.qweb; var _t = core._t; // the JobQueue schedules a sequence of 'jobs'. each job is @@ -434,6 +436,23 @@ var ProxyDevice = core.Class.extend(core.mixins.PropertiesMixin,{ send_printing_job(); }, + print_sale_details: function() { + var self = this; + new Model('report.point_of_sale.report_saledetails').call('get_sale_details').then(function(result){ + var env = { + company: self.pos.company, + pos: self.pos, + products: result.products, + payments: result.payments, + taxes: result.taxes, + total_paid: result.total_paid, + date: (new Date()).toLocaleString(), + }; + var report = QWeb.render('SaleDetailsReport', env); + self.print_receipt(report); + }) + }, + // asks the proxy to log some information, as with the debug.log you can provide several arguments. log: function(){ return this.message('log',{'arguments': _.toArray(arguments)}); diff --git a/addons/point_of_sale/static/src/js/report.js b/addons/point_of_sale/static/src/js/report.js new file mode 100644 index 00000000000..783ace7f655 --- /dev/null +++ b/addons/point_of_sale/static/src/js/report.js @@ -0,0 +1,63 @@ +odoo.define('point_of_sale.report', function (require) { +"use strict"; + +var chat_manager = require('mail.chat_manager'); +var composer = require('mail.composer'); +var ChatThread = require('mail.ChatThread'); +var utils = require('mail.utils'); + +var config = require('web.config'); +var core = require('web.core'); +var data = require('web.data'); +var Dialog = require('web.Dialog'); +var framework = require('web.framework'); +var Model = require('web.Model'); + +var pyeval = require('web.pyeval'); +var SearchView = require('web.SearchView'); + +var ActionManager = require('web.ActionManager'); +var ControlPanelMixin = require('web.ControlPanelMixin'); +var datepicker = require('web.datepicker'); +var Widget = require('web.Widget'); + +var QWeb = core.qweb; +var _t = core._t; + +// TODO replace in v10 with proper wizard +var PosDetailsWidget = Widget.extend(ControlPanelMixin, { + template: 'WizardSaleDetailsReport', + events: { + "click .js_generate": "renderReport", + }, + + renderElement: function() { + this._super(); + + var current_date = new moment(); + this.dp_start = new datepicker.DateWidget(this); + this.dp_start.insertBefore(this.$el.find('.js_date_start')); + this.dp_start.set_value(current_date.format('YYYY-MM-DD')); + + this.dp_stop = new datepicker.DateWidget(this); + this.dp_stop.insertAfter(this.$el.find('.js_date_stop')); + this.dp_stop.set_value(current_date.format('YYYY-MM-DD')); + + }, + + renderReport: function() { + var start = this.dp_start.get_value(); + var stop = this.dp_stop.get_value(); + var url = '/pos/sale_details_report?date_start='+start+'&date_stop='+stop; + window.open(url, '_blank'); + + }, + +}); + + +core.action_registry.add('report_pos_details', PosDetailsWidget); + +return PosDetailsWidget; + +}); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 0e54998e105..1ec188f7dfc 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -84,6 +84,14 @@ + +
+
+ +
+
+
+
@@ -829,6 +837,114 @@ + + + + +
+
+ +

+
+
+

+ + + +
+ + + + + + + + + + + + + + + + + Discount: % + + +
+ +
+ ------------------------ +
+ + + Payments: + + + + + + + + +
+ ------------------------ +
+ + + Taxes: + + + + + + + + +
+ ------------------------ +
+ + + Total: + + + + + +
+
+
+
+ +
+
+ + + +
+
+

Select Dates for the report

+ + + + + + + + + +
Start date
Stop date
+

+ +

+
+ + +
+
+ +