diff options
| author | Martin Trigaux <mat@odoo.com> | 2016-06-29 17:09:51 +0200 |
|---|---|---|
| committer | Martin Trigaux <mat@odoo.com> | 2016-09-08 16:37:01 +0200 |
| commit | ba070781b897a856e2f1d6cc9f735ee5da394e9d (patch) | |
| tree | 2d9eca4afcda83f9695a8d27d1960ed72acfd9c2 /addons | |
| parent | ff4549ec6a67060af7ab63fa64512b6cef6c7062 (diff) | |
[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
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/point_of_sale/__openerp__.py | 1 | ||||
| -rw-r--r-- | addons/point_of_sale/controllers/main.py | 7 | ||||
| -rw-r--r-- | addons/point_of_sale/models/pos_order.py | 104 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/js/chrome.js | 23 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/js/devices.js | 19 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/js/report.js | 63 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/xml/pos.xml | 116 | ||||
| -rw-r--r-- | addons/point_of_sale/views/point_of_sale_report.xml | 6 | ||||
| -rw-r--r-- | addons/point_of_sale/views/point_of_sale_template.xml | 2 | ||||
| -rw-r--r-- | addons/point_of_sale/views/pos_order_report_view.xml | 6 | ||||
| -rw-r--r-- | addons/point_of_sale/views/report_saledetails.xml | 84 |
11 files changed, 430 insertions, 1 deletions
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 | \*======================================*/ @@ -663,6 +680,11 @@ var Chrome = PosBaseWidget.extend({ '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, 'append': '.pos-rightheader', @@ -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 @@ </div> </t> + <t t-name="SaleDetailsButton"> + <div class="oe_status"> + <div class="js_connected oe_icon"> + <i class='fa fa-fw fa-print'></i> + </div> + </div> + </t> + <t t-name="HeaderButtonWidget"> <div class="header-button"> <t t-esc="widget.label" /> @@ -829,6 +837,114 @@ </receipt> </t> + <t t-name="SaleDetailsReport"> + <receipt align='center' width='40' value-thousands-separator='' > + <t t-if='pos.company_logo_base64'> + <img t-att-src='pos.company_logo_base64' /> + <br/> + </t> + <t t-if='!pos.company_logo_base64'> + <h1><t t-esc='company.name' /></h1> + <br/> + </t> + <br /><br /> + + <!-- Orderlines --> + + <div class='orderlines' line-ratio='0.6'> + <t t-foreach='products' t-as='line'> + <line> + <left><t t-esc='line.product_name' /></left> + <right> + <value value-decimals='2' value-autoint='on'> + <t t-esc='line.quantity' /> + </value> + <t t-if='line.uom !== "Unit(s)"'> + <t t-esc='line.uom' /> + </t> + </right> + <right> + <value><t t-esc='line.price_unit' /></value> + </right> + </line> + <t t-if='line.discount !== 0'> + <line indent='1'><left>Discount: <t t-esc='line.discount' />%</left></line> + </t> + </t> + </div> + + <br/> + <line><left>------------------------</left></line> + <br/> + + <line> + <left>Payments:</left> + </line> + <line t-foreach='payments' t-as='payment'> + <left><t t-esc='payment.name' /></left> + <right> + <t t-esc='payment.total'/> + </right> + </line> + + <br/> + <line><left>------------------------</left></line> + <br/> + + <line> + <left>Taxes:</left> + </line> + <line t-foreach='taxes' t-as='taxe'> + <left><t t-esc='taxe.name' /></left> + <right> + <t t-esc='taxe.total'/> + </right> + </line> + + <br/> + <line><left>------------------------</left></line> + <br/> + + <line> + <left>Total:</left> + <right> + <t t-esc='total_paid'/> + </right> + </line> + + <br/> + <div font='b'> + <div><t t-esc='date' /></div> + </div> + + </receipt> + </t> + + + <t t-name="WizardSaleDetailsReport"> + <div class="o_form_view"><div class="oe_form_sheetbg o_form_sheet_bg"><div class="oe_form_sheet oe_form_sheet_width o_form_sheet"> + <div class="pos_details_dates"> + <h3>Select Dates for the report</h3> + <table class="table-condensed"> + <tr> + <td><strong>Start date</strong></td> + <td><div class="js_date_start"></div></td> + </tr> + <tr> + <td><strong>Stop date</strong></td> + <td><div class="js_date_stop"></div></td> + </tr> + </table> + <p> + <button class="js_generate">Generate report</button> + </p> + </div> + + + </div></div></div> + </t> + + <t t-name="PopupWidget"> <div class="modal-dialog"> <div class="popup popup-alert"> diff --git a/addons/point_of_sale/views/point_of_sale_report.xml b/addons/point_of_sale/views/point_of_sale_report.xml index f9787c1b4c7..94ad5b1e811 100644 --- a/addons/point_of_sale/views/point_of_sale_report.xml +++ b/addons/point_of_sale/views/point_of_sale_report.xml @@ -17,5 +17,11 @@ <field name="report_name">point_of_sale.report_invoice</field> </record> + <record id="sale_details_report" model="ir.actions.report.xml"> + <field name="name">Sale Details</field> + <field name="model">report.point_of_sale.details</field> + <field name="report_type">qweb-pdf</field> + <field name="report_name">point_of_sale.report_saledetails</field> + </record> </odoo> diff --git a/addons/point_of_sale/views/point_of_sale_template.xml b/addons/point_of_sale/views/point_of_sale_template.xml index 979c8e98d4c..2081ea10ade 100644 --- a/addons/point_of_sale/views/point_of_sale_template.xml +++ b/addons/point_of_sale/views/point_of_sale_template.xml @@ -3,6 +3,8 @@ <template id="assets_backend" name="hr assets" inherit_id="web.assets_backend"> <xpath expr="." position="inside"> <link rel="stylesheet" href="/point_of_sale/static/src/less/pos_dashboard.less"/> + + <script type="text/javascript" src="/point_of_sale/static/src/js/report.js"></script> </xpath> </template> </odoo> diff --git a/addons/point_of_sale/views/pos_order_report_view.xml b/addons/point_of_sale/views/pos_order_report_view.xml index ce5aa141a4f..148b4e2b0bf 100644 --- a/addons/point_of_sale/views/pos_order_report_view.xml +++ b/addons/point_of_sale/views/pos_order_report_view.xml @@ -68,5 +68,11 @@ <field name="context">{'group_by_no_leaf':1,'group_by':[]}</field> </record> + <record id="action_report_pos_details" model="ir.actions.client"> + <field name="name">Sale Details</field> + <field name="tag">report_pos_details</field> + </record> + <menuitem id="menu_report_pos_order_all" name="Orders" action="action_report_pos_order_all" parent="menu_point_rep" sequence="3"/> + <menuitem id="menu_report_order_details" name="Sale Details" action="action_report_pos_details" parent="menu_point_rep" sequence="4"/> </odoo> diff --git a/addons/point_of_sale/views/report_saledetails.xml b/addons/point_of_sale/views/report_saledetails.xml new file mode 100644 index 00000000000..888eab47217 --- /dev/null +++ b/addons/point_of_sale/views/report_saledetails.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="utf-8"?> +<openerp> +<data> +<template id="report_saledetails"> + <t t-call="report.html_container"> + <t t-call="report.internal_layout"> + <div class="page"> + + <div class="text-center"> + <h2>Sale Details</h2> + + <strong><t t-esc="date_start"/> - <t t-esc="date_stop"/></strong> + </div> + + <!-- Orderlines --> + <h3>Products</h3> + <table class="table table-condensed"> + <thead><tr> + <th>Product</th> + <th>Quantity</th> + <th>Price Unit</th> + </tr></thead> + <tbody> + <tr t-foreach='products' t-as='line'> + <td><t t-esc="line['product_name']" /></td> + <td> + <t t-esc="line['quantity']" /> + <t t-if='line["uom"] != "Unit(s)"'> + <t t-esc='line["uom"]' /> + </t> + </td> + <td> + <t t-esc='line["price_unit"]' /> + <t t-if='line["discount"] != 0'> + Disc: <t t-esc='line["discount"]' />% + </t> + </td> + </tr> + </tbody> + </table> + + <br/> + + <h3>Payments</h3> + <table class="table table-condensed"> + <thead><tr> + <th>Name</th> + <th>Total</th> + </tr></thead> + <tbody> + <tr t-foreach='payments' t-as='payment'> + <td><t t-esc="payment['name']" /></td> + <td><t t-esc="payment['total']" /></td> + </tr> + </tbody> + </table> + + <br/> + + <h3>Taxes</h3> + <table class="table table-condensed"> + <thead><tr> + <th>Name</th> + <th>Total</th> + </tr></thead> + <tbody> + <tr t-foreach='taxes' t-as='tax'> + <td><t t-esc="tax['name']" /></td> + <td><t t-esc="tax['total']" /></td> + </tr> + </tbody> + </table> + + <br/> + <br/> + + <strong>Total: <t t-esc='total_paid'/></strong> + + </div> + </t> + </t> +</template> +</data> +</openerp> |
