Initial commit: Sale packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:49 +02:00
commit 14e3d26998
6469 changed files with 2479670 additions and 0 deletions

View file

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import pos_details
from . import pos_payment
from . import pos_close_session_wizard
from . import pos_session_check_product_wizard

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class PosCloseSessionWizard(models.TransientModel):
_name = "pos.close.session.wizard"
_description = "Close Session Wizard"
amount_to_balance = fields.Float("Amount to balance")
account_id = fields.Many2one("account.account", "Destination account")
account_readonly = fields.Boolean("Destination account is readonly")
message = fields.Text("Information message")
def close_session(self):
session = self.env["pos.session"].browse(self.env.context["active_ids"])
return session.action_pos_session_closing_control(
self.account_id, self.amount_to_balance
)

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_form_pos_close_session_wizard" model="ir.ui.view">
<field name="name">pos.close.session.wizard.form</field>
<field name="model">pos.close.session.wizard</field>
<field name="arch" type="xml">
<form string="Force Close Session">
<p><field name="message" readonly="1" /></p>
<group>
<field name="account_readonly" invisible="1" />
<field name="amount_to_balance" readonly="1" />
<field name="account_id" attrs="{'readonly': [('account_readonly', '==', True)]}"/>
</group>
<footer>
<button name="close_session" string="Close Session" type="object" class="btn-primary" data-hotkey="q"/>
<button special="cancel" data-hotkey="z" string="Cancel" class="btn-secondary" />
</footer>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class PosDetails(models.TransientModel):
_name = 'pos.details.wizard'
_description = 'Point of Sale Details Report'
def _default_start_date(self):
""" Find the earliest start_date of the latests sessions """
# restrict to configs available to the user
config_ids = self.env['pos.config'].search([]).ids
# exclude configs has not been opened for 2 days
self.env.cr.execute("""
SELECT
max(start_at) as start,
config_id
FROM pos_session
WHERE config_id = ANY(%s)
AND start_at > (NOW() - INTERVAL '2 DAYS')
GROUP BY config_id
""", (config_ids,))
latest_start_dates = [res['start'] for res in self.env.cr.dictfetchall()]
# earliest of the latest sessions
return latest_start_dates and min(latest_start_dates) or fields.Datetime.now()
start_date = fields.Datetime(required=True, default=_default_start_date)
end_date = fields.Datetime(required=True, default=fields.Datetime.now)
pos_config_ids = fields.Many2many('pos.config', 'pos_detail_configs',
default=lambda s: s.env['pos.config'].search([]))
@api.onchange('start_date')
def _onchange_start_date(self):
if self.start_date and self.end_date and self.end_date < self.start_date:
self.end_date = self.start_date
@api.onchange('end_date')
def _onchange_end_date(self):
if self.end_date and self.start_date and self.end_date < self.start_date:
self.start_date = self.end_date
def generate_report(self):
data = {'date_start': self.start_date, 'date_stop': self.end_date, 'config_ids': self.pos_config_ids.ids}
return self.env.ref('point_of_sale.sale_details_report').report_action([], data=data)

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_pos_details_wizard" model="ir.ui.view">
<field name="name">pos.details.wizard.form</field>
<field name="model">pos.details.wizard</field>
<field name="arch" type="xml">
<form string="Sales Details">
<group>
<field name="start_date"/>
<field name="end_date"/>
</group>
<group>
<field name="pos_config_ids" mode="tree" colspan="2" nolabel="1" />
</group>
<footer>
<button name="generate_report" string="Print" type="object" class="btn-primary" data-hotkey="q"/>
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z" />
</footer>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.tools import float_is_zero
from odoo.exceptions import UserError
class PosMakePayment(models.TransientModel):
_name = 'pos.make.payment'
_description = 'Point of Sale Make Payment Wizard'
def _default_config(self):
active_id = self.env.context.get('active_id')
if active_id:
return self.env['pos.order'].browse(active_id).session_id.config_id
return False
def _default_amount(self):
active_id = self.env.context.get('active_id')
if active_id:
order = self.env['pos.order'].browse(active_id)
amount_total = order.amount_total
# If we refund the entire order, we refund what was paid originally, else we refund the value of the items returned
if float_is_zero(order.refunded_order_ids.amount_total + order.amount_total, precision_rounding=order.currency_id.rounding):
amount_total = -order.refunded_order_ids.amount_paid
return amount_total - order.amount_paid
return False
def _default_payment_method(self):
active_id = self.env.context.get('active_id')
if active_id:
order_id = self.env['pos.order'].browse(active_id)
return order_id.session_id.payment_method_ids.sorted(lambda pm: pm.is_cash_count, reverse=True)[:1]
return False
config_id = fields.Many2one('pos.config', string='Point of Sale Configuration', required=True, default=_default_config)
amount = fields.Float(digits=0, required=True, default=_default_amount)
payment_method_id = fields.Many2one('pos.payment.method', string='Payment Method', required=True, default=_default_payment_method)
payment_name = fields.Char(string='Payment Reference')
payment_date = fields.Datetime(string='Payment Date', required=True, default=lambda self: fields.Datetime.now())
def check(self):
"""Check the order:
if the order is not paid: continue payment,
if the order is paid print ticket.
"""
self.ensure_one()
order = self.env['pos.order'].browse(self.env.context.get('active_id', False))
if self.payment_method_id.split_transactions and not order.partner_id:
raise UserError(_(
"Customer is required for %s payment method.",
self.payment_method_id.name
))
currency = order.currency_id
init_data = self.read()[0]
if not float_is_zero(init_data['amount'], precision_rounding=currency.rounding):
order.add_payment({
'pos_order_id': order.id,
'amount': order._get_rounded_amount(init_data['amount']),
'name': init_data['payment_name'],
'payment_method_id': init_data['payment_method_id'][0],
})
if order._is_pos_order_paid():
order.action_pos_order_paid()
order._create_order_picking()
order._compute_total_cost_in_real_time()
return {'type': 'ir.actions.act_window_close'}
return self.launch_payment()
def launch_payment(self):
return {
'name': _('Payment'),
'view_mode': 'form',
'res_model': 'pos.make.payment',
'view_id': False,
'target': 'new',
'views': False,
'type': 'ir.actions.act_window',
'context': self.env.context,
}

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_pos_payment" model="ir.ui.view">
<field name="name">pos.make.payment.form</field>
<field name="model">pos.make.payment</field>
<field name="arch" type="xml">
<form string="Pay Order">
<group>
<field name="config_id" invisible="1" />
<field name="payment_method_id" domain="[('config_ids', 'in', config_id)]"/>
<field name="amount" />
<field name="payment_name"/>
</group>
<footer>
<button name="check" string="Make Payment" type="object" class="btn-primary" data-hotkey="q"/>
<button special="cancel" data-hotkey="z" string="Cancel" class="btn-secondary"/>
</footer>
</form>
</field>
</record>
<record id="action_pos_payment" model="ir.actions.act_window">
<field name="name">Payment</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.make.payment</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo.exceptions import AccessDenied
from odoo.tools import convert
class PosSessionCheckProductWizard(models.TransientModel):
_name = 'pos.session.check_product_wizard'
_description = 'Verify if there are any products for the PoS'
def load_demo_products(self):
if not self.env.user.has_group("point_of_sale.group_pos_user"):
raise AccessDenied()
convert.convert_file(self.env.cr, 'point_of_sale', 'data/point_of_sale_onboarding.xml', None, mode='init', kind='data')
return self.open_ui()
def open_ui(self):
config = self.env['pos.config'].browse(self.env.context.get('config_id'))
return config._action_to_open_ui()

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_pos_session_check_product_wizard" model="ir.ui.view">
<field name="name">pos.session.check.product.wizard.form</field>
<field name="model">pos.session.check_product_wizard</field>
<field name="arch" type="xml">
<form string="Check products">
<p>
You can add some from the Products menu, or linked any existing by flagging them as "Available in PoS".
Or you can add demo data for testing purpose. Please mind that this is an irreversible action.
</p>
<footer>
<button name="load_demo_products" string="Add demo data" type="object" class="btn-primary" data-hotkey="q"/>
<button name="open_ui" string="Continue without Demo data" type="object" class="btn-secondary" data-hotkey="z" />
</footer>
</form>
</field>
</record>
</odoo>