[IMP] point_of_sale: fields added to X and Z reports

The X report of a PoS is a resume of the sales/refunds and other information of the session.
It can be printed at any moment of the session.
The Z report is the same as the X report with a bit more information like the starting and
the ending datetime of the session. It can only be printed after the end of a session.
The Z report can only be printed with the Swedish and Belgian localization at the moment
and the goal is to make it available in the standard version. This commit also adds some information to the
reports like the different invoices done during the session and also changes the layout of the reports
to be more readable.

closes odoo/odoo#113228

X-original-commit: b8b9067642fd75c2c02b202d9cffac98a4e150d4
Signed-off-by: Heinz Robin (rhe) <rhe@odoo.com>
This commit is contained in:
Adrien Guilliams (adgu)
2023-02-21 21:07:19 +01:00
parent 3c90a8555f
commit f8f4e899b5
9 changed files with 865 additions and 0 deletions
@@ -0,0 +1,2 @@
from . import models
from . import wizard
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Point of Sale Daily Sales Reports',
'version': '1.0',
'category': 'Point of Sale',
'sequence': 6,
'summary': 'Daily X and Z sales reports of a Point of Sale session',
'description': """
This module allows the cashier to quickly print a X and a Z sale report
for a given session or a Sales Details for multiple sessions
and configs.
""",
'depends': ['point_of_sale'],
'data': [
'security/ir.model.access.csv',
'wizard/pos_daily_sales_reports_wizard.xml',
'views/point_of_sale_view.xml',
],
'installable': True,
'license': 'LGPL-3',
}
@@ -0,0 +1 @@
from . import pos_daily_sales_reports
@@ -0,0 +1,370 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from odoo.osv.expression import AND
from datetime import timedelta
import pytz
class ReportSaleDetails(models.AbstractModel):
_inherit = 'report.point_of_sale.report_saledetails'
@api.model
def get_sale_details(self, date_start=False, date_stop=False, config_ids=False, session_ids=False):
""" Serialise the orders of the requested time period, configs and sessions.
:param date_start: The dateTime to start, default today 00:00:00.
:type date_start: str.
:param date_stop: The dateTime to stop, default date_start + 23:59:59.
:type date_stop: str.
:param config_ids: Pos Config id's to include.
:type config_ids: list of numbers.
:param session_ids: Pos Config id's to include.
:type session_ids: list of numbers.
:returns: dict -- Serialised sales.
"""
#This function is overriding the get_sale_details function in point_of_sale module
#It will be removed soon after the forwardport of the improvment of the X and Z reports.
domain = [('state', 'in', ['paid', 'invoiced', 'done'])]
if (session_ids):
domain = AND([domain, [('session_id', 'in', session_ids)]])
else:
if date_start:
date_start = fields.Datetime.from_string(date_start)
else:
# start by default today 00:00:00
user_tz = pytz.timezone(self.env.context.get('tz') or self.env.user.tz or 'UTC')
today = user_tz.localize(fields.Datetime.from_string(fields.Date.context_today(self)))
date_start = today.astimezone(pytz.timezone('UTC'))
if date_stop:
date_stop = fields.Datetime.from_string(date_stop)
# avoid a date_stop smaller than date_start
if (date_stop < date_start):
date_stop = date_start + timedelta(days=1, seconds=-1)
else:
# stop by default today 23:59:59
date_stop = date_start + timedelta(days=1, seconds=-1)
domain = AND([domain,
[('date_order', '>=', fields.Datetime.to_string(date_start)),
('date_order', '<=', fields.Datetime.to_string(date_stop))]
])
if config_ids:
domain = AND([domain, [('config_id', 'in', config_ids)]])
orders = self.env['pos.order'].search(domain)
user_currency = self.env.company.currency_id
total = 0.0
products_sold = {}
taxes = {}
refund_done = {}
refund_taxes = {}
for order in orders:
if user_currency != order.pricelist_id.currency_id:
total += order.pricelist_id.currency_id._convert(
order.amount_total, user_currency, order.company_id, order.date_order or fields.Date.today())
else:
total += order.amount_total
currency = order.session_id.currency_id
for line in order.lines:
if line.qty >= 0:
products_sold, taxes = self._get_products_and_taxes_dict(line, products_sold, taxes, currency)
else:
refund_done, refund_taxes = self._get_products_and_taxes_dict(line, refund_done, refund_taxes, currency)
payment_ids = self.env["pos.payment"].search([('pos_order_id', 'in', orders.ids)]).ids
if payment_ids:
self.env.cr.execute("""
SELECT method.id as id, payment.session_id as session, COALESCE(method.name->>%s, method.name->>'en_US') as name, method.is_cash_count as cash,
sum(amount) total, method.journal_id journal_id
FROM pos_payment AS payment,
pos_payment_method AS method
WHERE payment.payment_method_id = method.id
AND payment.id IN %s
GROUP BY method.name, method.is_cash_count, payment.session_id, method.id, journal_id
""", (self.env.lang, tuple(payment_ids),))
payments = self.env.cr.dictfetchall()
else:
payments = []
configs = []
sessions = []
if config_ids:
configs = self.env['pos.config'].search([('id', 'in', config_ids)])
if session_ids:
sessions = self.env['pos.session'].search([('id', 'in', session_ids)])
else:
sessions = self.env['pos.session'].search([('config_id', 'in', configs.ids), ('start_at', '>=', date_start), ('stop_at', '<=', date_stop)])
else:
sessions = self.env['pos.session'].search([('id', 'in', session_ids)])
for session in sessions:
configs.append(session.config_id)
for payment in payments:
payment['count'] = False
for session in sessions:
cash_counted = 0
if session.cash_register_balance_end_real:
cash_counted = session.cash_register_balance_end_real
for payment in payments:
account_payments = self.env['account.payment'].search([('pos_session_id', '=', session.id)])
if payment['session'] == session.id:
if not payment['cash']:
for account_payment in account_payments:
if payment['id'] == account_payment.pos_payment_method_id.id:
payment['final_count'] = payment['total']
payment['money_counted'] = account_payment.amount
payment['money_difference'] = payment['money_counted'] - payment['final_count']
payment['cash_moves'] = []
if payment['money_difference'] > 0:
move_name = 'Difference observed during the counting (Profit)'
payment['cash_moves'] = [{'name': move_name, 'amount': payment['money_difference']}]
elif payment['money_difference'] < 0:
move_name = 'Difference observed during the counting (Loss)'
payment['cash_moves'] = [{'name': move_name, 'amount': payment['money_difference']}]
payment['count'] = True
break
else:
payment['final_count'] = payment['total'] + session.cash_register_balance_start + session.cash_real_transaction
payment['money_counted'] = cash_counted
payment['money_difference'] = payment['money_counted'] - payment['final_count']
cash_moves = self.env['account.bank.statement.line'].search([('pos_session_id', '=', session.id)])
cash_in_out_list = []
cash_in_count = 0
cash_out_count = 0
if session.cash_register_balance_start > 0:
cash_in_out_list.append({
'name': 'Cash Opening',
'amount': session.cash_register_balance_start,
})
for cash_move in cash_moves:
if cash_move.amount > 0:
cash_in_count += 1
name = f'Cash in {cash_in_count}'
else:
cash_out_count += 1
name = f'Cash out {cash_out_count}'
if cash_move.move_id.journal_id.id == payment['journal_id']:
cash_in_out_list.append({
'name': cash_move.payment_ref if cash_move.payment_ref else name,
'amount': cash_move.amount
})
payment['cash_moves'] = cash_in_out_list
payment['count'] = True
products = []
refund_products = []
for category_name, product_list in products_sold.items():
category_dictionnary = {
'name': category_name,
'products': sorted([{
'product_id': product.id,
'product_name': product.name,
'code': product.default_code,
'quantity': qty,
'price_unit': price_unit,
'discount': discount,
'uom': product.uom_id.name
} for (product, price_unit, discount), qty in product_list.items()], key=lambda l: l['product_name']),
}
products.append(category_dictionnary)
products = sorted(products, key=lambda l: str(l['name']))
for category_name, product_list in refund_done.items():
category_dictionnary = {
'name': category_name,
'products': sorted([{
'product_id': product.id,
'product_name': product.name,
'code': product.default_code,
'quantity': qty,
'price_unit': price_unit,
'discount': discount,
'uom': product.uom_id.name
} for (product, price_unit, discount), qty in product_list.items()], key=lambda l: l['product_name']),
}
refund_products.append(category_dictionnary)
refund_products = sorted(refund_products, key=lambda l: str(l['name']))
products, products_info = self._get_total_and_qty_per_category(products)
refund_products, refund_info = self._get_total_and_qty_per_category(refund_products)
currency = {
'symbol': user_currency.symbol,
'position': True if user_currency.position == 'after' else False,
'total_paid': user_currency.round(total),
'precision': user_currency.decimal_places,
}
session_name = False
if len(sessions) == 1:
state = sessions[0].state
date_start = sessions[0].start_at
date_stop = sessions[0].stop_at
session_name = sessions[0].name
else:
state = "multiple"
config_names = []
for config in configs:
config_names.append(config.name)
discount_number = 0
discount_amount = 0
invoiceList = []
invoiceTotal = 0
for session in sessions:
discount_number += len(session.order_ids.filtered(lambda o: o.lines.filtered(lambda l: l.discount > 0)))
discount_amount += session.get_total_discount()
invoiceList.append({
'name': session.name,
'invoices': session._get_invoice_total_list()
})
invoiceTotal += session._get_total_invoice()
return {
'opening_note': sessions[0].opening_notes if len(sessions) == 1 else False,
'closing_note': sessions[0].closing_notes if len(sessions) == 1 else False,
'state': state,
'currency': currency,
'nbr_orders': len(orders),
'date_start': date_start,
'date_stop': date_stop,
'session_name': session_name if session_name else False,
'config_names': config_names,
'payments': payments,
'company_name': self.env.company.name,
'taxes': list(taxes.values()),
'products': products,
'products_info': products_info,
'refund_taxes': list(refund_taxes.values()),
'refund_info': refund_info,
'refund_products': refund_products,
'discount_number': discount_number,
'discount_amount': discount_amount,
'invoiceList': invoiceList,
'invoiceTotal': invoiceTotal,
}
def _get_products_and_taxes_dict(self, line, products, taxes, currency):
key1 = line.product_id.product_tmpl_id.pos_categ_id.name
key2 = (line.product_id, line.price_unit, line.discount)
products.setdefault(key1, {})
products[key1].setdefault(key2, 0.0)
products[key1][key2] += line.qty
if line.tax_ids_after_fiscal_position:
line_taxes = line.tax_ids_after_fiscal_position.sudo().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'], 'tax_amount':0.0, 'base_amount':0.0})
taxes[tax['id']]['tax_amount'] += tax['amount']
taxes[tax['id']]['base_amount'] += tax['base']
else:
taxes.setdefault(0, {'name': _('No Taxes'), 'tax_amount':0.0, 'base_amount':0.0})
taxes[0]['base_amount'] += line.price_subtotal_incl
return products, taxes
def _get_total_and_qty_per_category(self, categories):
all_qty = 0
all_total = 0
for category_dict in categories:
qty_cat = 0
total_cat = 0
for product in category_dict['products']:
qty_cat += product['quantity']
total_cat += (product['quantity'] * product['price_unit']) * (100 - product['discount']) / 100
product['total_paid'] = (product['quantity'] * product['price_unit']) * (100 - product['discount']) / 100
category_dict['total'] = total_cat
category_dict['qty'] = qty_cat
all_qty += qty_cat
all_total += total_cat
return categories, {'total': all_total, 'qty': all_qty}
class PosSession(models.Model):
_inherit = 'pos.session'
closing_notes = fields.Text(string='Closing Notes')
def update_closing_control_state_session(self, notes):
# Prevent closing the session again if it was already closed
if self.state == 'closed':
raise UserError(_('This session is already closed.'))
# Prevent the session to be opened again.
self.write({'state': 'closing_control', 'stop_at': fields.Datetime.now(), 'closing_notes': notes})
self._post_cash_details_message('Closing', self.cash_register_difference, notes)
def get_total_discount(self):
amount = 0
for line in self.env['pos.order.line'].search([('order_id', 'in', self.order_ids.ids), ('discount', '>', 0)]):
normal_price = line.qty * line.price_unit
normal_price = normal_price + (normal_price / 100 * line.tax_ids.amount)
amount += normal_price - line.price_subtotal_incl
return amount
def _get_invoice_total_list(self):
invoice_list = []
for order in self.order_ids.filtered(lambda o: o.is_invoiced):
invoice = {
'total': order.account_move.amount_total,
'name': order.account_move.highest_name,
'order_ref': order.pos_reference
}
invoice_list.append(invoice)
return invoice_list
def _get_total_invoice(self):
amount = 0
for order in self.order_ids.filtered(lambda o: o.is_invoiced):
amount += order.amount_paid
return amount
def get_total_sold_refund_per_category(self, group_by_user_id=None):
total_sold_per_user_per_category = {}
total_refund_per_user_per_category = {}
for order in self.order_ids:
if group_by_user_id:
user_id = order.user_id.id
else:
# use a user_id of 0 to keep the logic between with user group and without user group the same
user_id = 0
if user_id not in total_sold_per_user_per_category:
total_sold_per_user_per_category[user_id] = {}
total_refund_per_user_per_category[user_id] = {}
total_sold_per_category = total_sold_per_user_per_category[user_id]
total_refund_per_category = total_refund_per_user_per_category[user_id]
for line in order.lines:
key = line.product_id.pos_categ_id.name or "None"
if line.qty >= 0:
if key in total_sold_per_category:
total_sold_per_category[key] += line.price_subtotal_incl
else:
total_sold_per_category[key] = line.price_subtotal_incl
else:
if key in total_refund_per_category:
total_refund_per_category[key] += line.price_subtotal_incl
else:
total_refund_per_category[key] = line.price_subtotal_incl
if group_by_user_id or not total_sold_per_user_per_category:
return list(total_sold_per_user_per_category.items()), list(total_refund_per_user_per_category.items())
else:
return list(total_sold_per_user_per_category[0].items()), list(total_refund_per_user_per_category[0].items())
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_pos_daily_sales_reports_wizard,access.pos.daily.sales.reports.wizard,model_pos_daily_sales_reports_wizard,point_of_sale.group_pos_manager,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_pos_daily_sales_reports_wizard access.pos.daily.sales.reports.wizard model_pos_daily_sales_reports_wizard point_of_sale.group_pos_manager 1 1 1 0
@@ -0,0 +1,424 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<menuitem id="menu_report_daily_details"
name="Session Report"
action="action_report_pos_daily_sales_reports"
parent="point_of_sale.menu_point_rep"
sequence="5"/>
<template id="pos_daily_report" inherit_id="point_of_sale.report_saledetails">
<xpath expr="//t[@t-call='web.internal_layout']" position="replace">
<t t-call="web.internal_layout">
<div class="page">
<div class="text-center">
<t t-if="state == 'multiple'">
<h2>Sales Details</h2>
<t t-if="config_names">
<strong>Config names</strong>
<t t-foreach="config_names" t-as="name">
<t t-esc="name" />,
</t>
</t>
</t>
<t t-else="">
<h2>Daily Report
<t t-if='state == "closed"'>Z</t>
<t t-else="">X</t>
</h2>
<t t-if="session_name">
Session ID: <t t-esc="session_name" />
</t>
</t>
<br/>
<t t-if="date_start and date_stop">
<strong><t t-esc="date_start" t-options="{'widget': 'datetime'}"/> - <t t-esc="date_stop" t-options="{'widget': 'datetime'}"/></strong>
</t>
</div>
<br/>
<br/>
<!-- Orderlines -->
<t t-if="products" id="sales">
<h3>Sales</h3>
<table class="table table-sm">
<thead><tr>
<th>Product Category</th>
<th>Product</th>
<th>Quantity</th>
<th>Total</th>
</tr></thead>
<tbody>
<tr t-foreach="products" t-as="category">
<td style="background-color: #dadada;"><t t-esc="category['name']" /></td>
<td style="background-color: #dadada;"/>
<td style="background-color: #dadada;">
<t t-esc="category['qty']"/>
</td>
<td style="background-color: #dadada;">
<t t-if="currency['position']">
<t t-esc="category['total']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="category['total']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
<tr t-foreach="category['products']" t-as="line">
<td/>
<t t-set="internal_reference" t-value="line['code'] and '[%s] ' % line['code'] or ''" />
<td><t t-esc="internal_reference" /><t t-esc="line['product_name']" /></td>
<td>
<t t-esc="line['quantity']" />
<t t-if='line["uom"] != "Units"'>
<t t-esc='line["uom"]' />
</t>
</td>
<td>
<t t-if="currency['position']">
<t t-esc='line["total_paid"]' t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc='line["total_paid"]' t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
<t t-if='line["discount"] != 0'>
Disc: <t t-esc='line["discount"]' />%
</t>
</td>
</tr>
</tr>
<tr>
<td style="background-color: #b1b1b1;"><strong>Total</strong></td>
<td style="background-color: #b1b1b1;"/>
<td style="background-color: #b1b1b1;">
<strong><t t-esc="products_info['qty']"/></strong>
</td>
<td style="background-color: #b1b1b1;">
<t t-if="currency['position']">
<strong><t t-esc='products_info["total"]' t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' /></strong>
</t>
<t t-else="" >
<strong><t t-esc='currency["symbol"]' /><t t-esc='products_info["total"]' t-options="{'widget': 'float', 'precision': currency['precision']}"/></strong>
</t>
</td>
</tr>
</tbody>
</table>
</t>
<t t-if="taxes" id="taxe_sales">
<h3>Taxes on sales</h3>
<table class="table table-sm">
<thead><tr>
<th>Name</th>
<th>Tax Amount</th>
<th>Base Amount</th>
</tr></thead>
<tbody>
<tr t-foreach='taxes' t-as='tax'>
<td><t t-esc="tax['name']" /></td>
<td>
<t t-if="currency['position']">
<t t-esc="tax['tax_amount']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="tax['tax_amount']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
<td>
<t t-if="currency['position']">
<t t-esc="tax['base_amount']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="tax['base_amount']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
</tr>
</tbody>
</table>
</t>
<br/>
<t t-if="refund_products" id="refunds">
<h3>Refunds</h3>
<table class="table table-sm">
<thead><tr>
<th>Product Category</th>
<th>Product</th>
<th>Quantity</th>
<th>Total</th>
</tr></thead>
<tbody>
<tr t-foreach="refund_products" t-as="category">
<td style="background-color: #dadada;"><t t-esc="category['name']" /></td>
<td style="background-color: #dadada;"/>
<td style="background-color: #dadada;">
<t t-esc="category['qty']"/>
</td>
<td style="background-color: #dadada;">
<t t-if="currency['position']">
<t t-esc="category['total']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="category['total']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
<tr t-foreach="category['products']" t-as="line">
<td/>
<t t-set="internal_reference" t-value="line['code'] and '[%s] ' % line['code'] or ''" />
<td><t t-esc="internal_reference" /><t t-esc="line['product_name']" /></td>
<td>
<t t-esc="line['quantity']" />
<t t-if='line["uom"] != "Units"'>
<t t-esc='line["uom"]' />
</t>
</td>
<td>
<t t-if="currency['position']">
<t t-esc='line["total_paid"]' t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc='line["total_paid"]' t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
<t t-if='line["discount"] != 0'>
Disc: <t t-esc='line["discount"]' />%
</t>
</td>
</tr>
</tr>
<tr>
<td style="background-color: #b1b1b1;"><strong>Total</strong></td>
<td style="background-color: #b1b1b1;"/>
<td style="background-color: #b1b1b1;">
<strong><t t-esc="refund_info['qty']"/></strong>
</td>
<td style="background-color: #b1b1b1;">
<t t-if="currency['position']">
<strong><t t-esc='refund_info["total"]' t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' /></strong>
</t>
<t t-else="" >
<strong><t t-esc='currency["symbol"]' /><t t-esc='refund_info["total"]' t-options="{'widget': 'float', 'precision': currency['precision']}"/></strong>
</t>
</td>
</tr>
</tbody>
</table>
</t>
<t t-if="refund_taxes" id="taxe_refunds">
<h3>Taxes on refunds</h3>
<table class="table table-sm">
<thead><tr>
<th>Name</th>
<th>Tax Amount</th>
<th>Base Amount</th>
</tr></thead>
<tbody>
<tr t-foreach='refund_taxes' t-as='tax'>
<td><t t-esc="tax['name']" /></td>
<td>
<t t-if="currency['position']">
<t t-esc="tax['tax_amount']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="tax['tax_amount']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
<td>
<t t-if="currency['position']">
<t t-esc="tax['base_amount']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="tax['base_amount']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
</tr>
</tbody>
</table>
</t>
<br/>
<t t-if="payments" id="payments">
<t t-if="payments" id="other_payments">
<h3>Payments</h3>
<table class="table table-sm">
<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-if="currency['position']">
<t t-esc="payment['total']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="payment['total']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
</tr>
</tbody>
</table>
</t>
</t>
<br/>
<br/>
<div id="discounts">
<h5>Discounts:</h5>
<div class="row">
<div class="col-12">
<strong>Number of discounts</strong>:
<t t-esc="discount_number"/>
</div>
</div>
<div class="row">
<div class="col-12">
<strong>Amount of discounts</strong>:
<t t-esc="discount_amount"/>
</div>
</div>
<br/>
</div>
</div>
<t t-if="invoiceTotal" id="invoices">
<br/>
<br/>
<h3>Invoices</h3>
<table class="table table-sm">
<thead><tr>
<th>Name</th>
<th>Order reference</th>
<th>Total</th>
</tr></thead>
<tbody>
<t t-foreach="invoiceList" t-as="invoiceSession">
<t t-if="invoiceSession['invoices']">
<tr t-foreach='invoiceSession["invoices"]' t-as='invoice'>
<td><t t-esc="invoice['name']" /></td>
<td><t t-esc="invoice['order_ref']" /></td>
<td>
<t t-if="currency['position']">
<t t-esc="invoice['total']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="invoice['total']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
</tr>
</t>
</t>
<tr>
<td style="background-color: #b1b1b1;"><strong>Total</strong></td>
<td style="background-color: #b1b1b1;"/>
<td style="background-color: #b1b1b1;"><strong>
<t t-if="currency['position']">
<t t-esc="invoiceTotal" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="invoiceTotal" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</strong></td>
</tr>
</tbody>
</table>
</t>
<t t-if="(state == 'closed' or state == 'multiple') and payments" id="closing_session">
<h3>Session Control</h3>
<br/>
<strong>Total:
<t t-if="currency['position']">
<t t-esc='currency["total_paid"]' t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc='currency["total_paid"]' t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</strong>
<br/>
<strong>Number of transactions: <t t-esc="nbr_orders" /></strong>
<br/>
<br/>
<table class="table table-sm">
<thead><tr>
<th>Name</th>
<th/>
<th>Expected</th>
<th>Counted</th>
<th>Difference</th>
</tr></thead>
<tbody>
<t t-foreach="payments" t-as="method">
<t t-if="method['count']">
<tr>
<td><strong><t t-esc="method['name']"/></strong></td>
<td/>
<td>
<t t-if="currency['position']">
<t t-esc="method['final_count']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="method['final_count']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
<td>
<t t-if="currency['position']">
<t t-esc="method['money_counted']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="method['money_counted']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
<td>
<t t-if="currency['position']">
<t t-esc="method['money_difference']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="method['money_difference']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
</tr>
<tr t-foreach="method['cash_moves']" t-as="move">
<td/>
<td><t t-esc="move['name']"/></td>
<td>
<t t-if="currency['position']">
<t t-esc="move['amount']" t-options="{'widget': 'float', 'precision': currency['precision']}"/><t t-esc='currency["symbol"]' />
</t>
<t t-else="" >
<t t-esc='currency["symbol"]' /><t t-esc="move['amount']" t-options="{'widget': 'float', 'precision': currency['precision']}"/>
</t>
</td>
<td></td>
<td></td>
</tr>
</t>
</t>
</tbody>
</table>
<t t-if="opening_note or closing_note">
<br/>
<br/>
<t t-if="opening_note" id="opening_note">
<strong>Opening of session note:</strong>
<t t-esc="opening_note" />
</t>
<br/>
<t t-if="closing_note" id="closing_note">
<strong>End of session note:</strong>
<t t-esc="closing_note" />
</t>
</t>
</t>
</t>
</xpath>
</template>
</odoo>
@@ -0,0 +1 @@
from . import pos_daily_sales_reports_wizard
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class PosDailyReport(models.TransientModel):
_name = 'pos.daily.sales.reports.wizard'
_description = 'Point of Sale Daily Report'
pos_session_id = fields.Many2one('pos.session')
def generate_report(self):
data = {'date_start': False, 'date_stop': False, 'config_ids': self.pos_session_id.config_id.ids, 'session_ids': self.pos_session_id.ids}
return self.env.ref('point_of_sale.sale_details_report').report_action([], data=data)
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_pos_daily_sales_reports_wizard" model="ir.ui.view">
<field name="name">pos.daily.sales.reports.wizard.form</field>
<field name="model">pos.daily.sales.reports.wizard</field>
<field name="arch" type="xml">
<form string="Sales Details">
<label string="Pos session" for="pos_session_id"/>
<field name="pos_session_id" mode="tree" colspan="4"/>
<footer>
<button name="generate_report" string="Print" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_report_pos_daily_sales_reports" model="ir.actions.act_window">
<field name="name">Daily Reports</field>
<field name="res_model">pos.daily.sales.reports.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>