mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 13:52:00 +02:00
Initial commit: Sale packages
This commit is contained in:
commit
14e3d26998
6469 changed files with 2479670 additions and 0 deletions
10
odoo-bringout-oca-ocb-pos_sale/pos_sale/models/__init__.py
Normal file
10
odoo-bringout-oca-ocb-pos_sale/pos_sale/models/__init__.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import pos_config
|
||||
from . import pos_order
|
||||
from . import crm_team
|
||||
from . import pos_session
|
||||
from . import sale_order
|
||||
from . import stock_picking
|
||||
from . import res_config_settings
|
||||
31
odoo-bringout-oca-ocb-pos_sale/pos_sale/models/crm_team.py
Normal file
31
odoo-bringout-oca-ocb-pos_sale/pos_sale/models/crm_team.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# -*- 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, ValidationError
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
|
||||
|
||||
class CrmTeam(models.Model):
|
||||
_inherit = 'crm.team'
|
||||
|
||||
pos_config_ids = fields.One2many('pos.config', 'crm_team_id', string="Point of Sales")
|
||||
pos_sessions_open_count = fields.Integer(string='Open POS Sessions', compute='_compute_pos_sessions_open_count')
|
||||
pos_order_amount_total = fields.Float(string="Session Sale Amount", compute='_compute_pos_order_amount_total')
|
||||
|
||||
def _compute_pos_sessions_open_count(self):
|
||||
for team in self:
|
||||
team.pos_sessions_open_count = self.env['pos.session'].search_count([('config_id.crm_team_id', '=', team.id), ('state', '=', 'opened')])
|
||||
|
||||
def _compute_pos_order_amount_total(self):
|
||||
data = self.env['report.pos.order']._read_group([
|
||||
('session_id.state', '=', 'opened'),
|
||||
('config_id.crm_team_id', 'in', self.ids),
|
||||
], ['price_total:sum', 'config_id'], ['config_id'])
|
||||
rg_results = dict((d['config_id'][0], d['price_total']) for d in data)
|
||||
for team in self:
|
||||
team.pos_order_amount_total = sum([
|
||||
rg_results.get(config.id, 0.0)
|
||||
for config in team.pos_config_ids
|
||||
])
|
||||
15
odoo-bringout-oca-ocb-pos_sale/pos_sale/models/pos_config.py
Normal file
15
odoo-bringout-oca-ocb-pos_sale/pos_sale/models/pos_config.py
Normal file
|
|
@ -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 PosConfig(models.Model):
|
||||
_inherit = 'pos.config'
|
||||
|
||||
crm_team_id = fields.Many2one(
|
||||
'crm.team', string="Sales Team", ondelete="set null",
|
||||
help="This Point of sale's sales will be related to this Sales Team.")
|
||||
down_payment_product_id = fields.Many2one('product.product',
|
||||
string="Down Payment Product",
|
||||
help="This product will be used as down payment on a sale order.")
|
||||
168
odoo-bringout-oca-ocb-pos_sale/pos_sale/models/pos_order.py
Normal file
168
odoo-bringout-oca-ocb-pos_sale/pos_sale/models/pos_order.py
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from functools import lru_cache
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import float_compare, float_is_zero
|
||||
|
||||
|
||||
class PosOrder(models.Model):
|
||||
_inherit = 'pos.order'
|
||||
|
||||
currency_rate = fields.Float(compute='_compute_currency_rate', store=True, digits=0, readonly=True)
|
||||
crm_team_id = fields.Many2one('crm.team', string="Sales Team", ondelete="set null")
|
||||
sale_order_count = fields.Integer(string='Sale Order Count', compute='_count_sale_order', readonly=True, groups="sales_team.group_sale_salesman")
|
||||
|
||||
def _count_sale_order(self):
|
||||
for order in self:
|
||||
order.sale_order_count = len(order.lines.mapped('sale_order_origin_id'))
|
||||
|
||||
@api.model
|
||||
def _complete_values_from_session(self, session, values):
|
||||
values = super(PosOrder, self)._complete_values_from_session(session, values)
|
||||
values.setdefault('crm_team_id', session.config_id.crm_team_id.id)
|
||||
return values
|
||||
|
||||
@api.depends('pricelist_id.currency_id', 'date_order', 'company_id')
|
||||
def _compute_currency_rate(self):
|
||||
@lru_cache
|
||||
def get_rate(from_currency, to_currency, company, date):
|
||||
return self.env['res.currency']._get_conversion_rate(
|
||||
from_currency=from_currency,
|
||||
to_currency=to_currency,
|
||||
company=company,
|
||||
date=date,
|
||||
)
|
||||
for order in self:
|
||||
date_order = order.date_order or fields.Datetime.now()
|
||||
# date_order is a datetime, but the rates are looked up on a date basis,
|
||||
# therefor converting the date_order to a date helps with sharing entries in the lru_cache
|
||||
order.currency_rate = get_rate(order.company_id.currency_id, order.pricelist_id.currency_id, order.company_id, date_order.date())
|
||||
|
||||
def _prepare_invoice_vals(self):
|
||||
invoice_vals = super(PosOrder, self)._prepare_invoice_vals()
|
||||
invoice_vals['team_id'] = self.crm_team_id.id
|
||||
sale_orders = self.lines.mapped('sale_order_origin_id')
|
||||
if sale_orders:
|
||||
if sale_orders[0].partner_invoice_id.id != sale_orders[0].partner_shipping_id.id:
|
||||
invoice_vals['partner_shipping_id'] = sale_orders[0].partner_shipping_id.id
|
||||
else:
|
||||
addr = self.partner_id.address_get(['delivery'])
|
||||
invoice_vals['partner_shipping_id'] = addr['delivery']
|
||||
if sale_orders[0].payment_term_id:
|
||||
invoice_vals['invoice_payment_term_id'] = sale_orders[0].payment_term_id.id
|
||||
if sale_orders[0].partner_invoice_id != sale_orders[0].partner_id:
|
||||
invoice_vals['partner_id'] = sale_orders[0].partner_invoice_id.id
|
||||
return invoice_vals
|
||||
|
||||
def _create_order_picking(self):
|
||||
for line in self.lines.filtered(lambda l: l.product_id == self.config_id.down_payment_product_id and l.qty != 0 and (l.sale_order_origin_id or l.refunded_orderline_id.sale_order_origin_id)):
|
||||
sale_lines = line.sale_order_origin_id.order_line or line.refunded_orderline_id.sale_order_origin_id.order_line
|
||||
sale_order_origin = line.sale_order_origin_id or line.refunded_orderline_id.sale_order_origin_id
|
||||
sale_line = self.env['sale.order.line'].create({
|
||||
'order_id': sale_order_origin.id,
|
||||
'product_id': line.product_id.id,
|
||||
'price_unit': line.price_unit,
|
||||
'product_uom_qty': 0,
|
||||
'tax_id': [(6, 0, line.tax_ids.ids)],
|
||||
'is_downpayment': True,
|
||||
'discount': line.discount,
|
||||
'sequence': sale_lines and sale_lines[-1].sequence + 1 or 10,
|
||||
})
|
||||
line.sale_order_line_id = sale_line
|
||||
|
||||
so_lines = self.lines.mapped('sale_order_line_id')
|
||||
|
||||
# confirm the unconfirmed sale orders that are linked to the sale order lines
|
||||
sale_orders = so_lines.mapped('order_id')
|
||||
for sale_order in sale_orders.filtered(lambda so: so.state in ['draft', 'sent']):
|
||||
sale_order.action_confirm()
|
||||
|
||||
# update the demand qty in the stock moves related to the sale order line
|
||||
# flush the qty_delivered to make sure the updated qty_delivered is used when
|
||||
# updating the demand value
|
||||
so_lines.flush_recordset(['qty_delivered'])
|
||||
# track the waiting pickings
|
||||
waiting_picking_ids = set()
|
||||
for so_line in so_lines:
|
||||
so_line_stock_move_ids = so_line.move_ids.group_id.stock_move_ids
|
||||
for stock_move in so_line.move_ids:
|
||||
picking = stock_move.picking_id
|
||||
if picking.state not in ['waiting', 'confirmed', 'assigned']:
|
||||
continue
|
||||
new_qty = so_line.product_uom_qty - so_line.qty_delivered
|
||||
if float_compare(new_qty, 0, precision_rounding=stock_move.product_uom.rounding) <= 0:
|
||||
new_qty = 0
|
||||
stock_move.product_uom_qty = so_line.compute_uom_qty(new_qty, stock_move, False)
|
||||
# If the product is delivered with more than one step, we need to update the quantity of the other steps
|
||||
for move in so_line_stock_move_ids.filtered(lambda m: m.state in ['waiting', 'confirmed', 'assigned'] and m.product_id == stock_move.product_id):
|
||||
move.product_uom_qty = stock_move.product_uom_qty
|
||||
waiting_picking_ids.add(move.picking_id.id)
|
||||
waiting_picking_ids.add(picking.id)
|
||||
|
||||
def is_product_uom_qty_zero(move):
|
||||
return float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding)
|
||||
|
||||
# cancel the waiting pickings if each product_uom_qty of move is zero
|
||||
for picking in self.env['stock.picking'].browse(waiting_picking_ids):
|
||||
if all(is_product_uom_qty_zero(move) for move in picking.move_ids):
|
||||
picking.action_cancel()
|
||||
return super()._create_order_picking()
|
||||
|
||||
def action_view_sale_order(self):
|
||||
self.ensure_one()
|
||||
linked_orders = self.lines.mapped('sale_order_origin_id')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Linked Sale Orders'),
|
||||
'res_model': 'sale.order',
|
||||
'view_mode': 'tree,form',
|
||||
'domain': [('id', 'in', linked_orders.ids)],
|
||||
}
|
||||
|
||||
def _get_fields_for_order_line(self):
|
||||
fields = super(PosOrder, self)._get_fields_for_order_line()
|
||||
fields.extend([
|
||||
'sale_order_origin_id',
|
||||
'down_payment_details',
|
||||
'sale_order_line_id',
|
||||
])
|
||||
return fields
|
||||
|
||||
def _prepare_order_line(self, order_line):
|
||||
order_line = super()._prepare_order_line(order_line)
|
||||
if order_line.get('sale_order_origin_id'):
|
||||
order_line['sale_order_origin_id'] = {
|
||||
'id': order_line['sale_order_origin_id'][0],
|
||||
'name': order_line['sale_order_origin_id'][1],
|
||||
}
|
||||
if order_line.get('sale_order_line_id'):
|
||||
order_line['sale_order_line_id'] = {
|
||||
'id': order_line['sale_order_line_id'][0],
|
||||
}
|
||||
return order_line
|
||||
|
||||
class PosOrderLine(models.Model):
|
||||
_inherit = 'pos.order.line'
|
||||
|
||||
sale_order_origin_id = fields.Many2one('sale.order', string="Linked Sale Order")
|
||||
sale_order_line_id = fields.Many2one('sale.order.line', string="Source Sale Order Line")
|
||||
down_payment_details = fields.Text(string="Down Payment Details")
|
||||
|
||||
def _export_for_ui(self, orderline):
|
||||
result = super()._export_for_ui(orderline)
|
||||
# NOTE We are not exporting 'sale_order_line_id' because it is being used in any views in the POS App.
|
||||
result['down_payment_details'] = bool(orderline.down_payment_details) and orderline.down_payment_details
|
||||
result['sale_order_origin_id'] = bool(orderline.sale_order_origin_id) and orderline.sale_order_origin_id.read(fields=['name'])[0]
|
||||
return result
|
||||
|
||||
def _order_line_fields(self, line, session_id=None):
|
||||
result = super()._order_line_fields(line, session_id)
|
||||
vals = result[2]
|
||||
if vals.get('sale_order_origin_id', False):
|
||||
vals['sale_order_origin_id'] = vals['sale_order_origin_id']['id']
|
||||
if vals.get('sale_order_line_id', False):
|
||||
#We need to make sure the order line has not been deleted while the order was being handled in the PoS
|
||||
order_line = self.env['sale.order.line'].search([('id', '=', vals['sale_order_line_id']['id'])], limit=1)
|
||||
vals['sale_order_line_id'] = order_line.id if order_line else False
|
||||
return result
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo.osv.expression import OR
|
||||
|
||||
|
||||
class PosSession(models.Model):
|
||||
_inherit = 'pos.session'
|
||||
|
||||
crm_team_id = fields.Many2one('crm.team', related='config_id.crm_team_id', string="Sales Team", readonly=True)
|
||||
|
||||
def _loader_params_product_product(self):
|
||||
result = super()._loader_params_product_product()
|
||||
result['search_params']['domain'] = OR([result['search_params']['domain'], [('id', '=', self.config_id.down_payment_product_id.id)]])
|
||||
result['search_params']['fields'].extend(['invoice_policy', 'type'])
|
||||
return result
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
pos_crm_team_id = fields.Many2one(related='pos_config_id.crm_team_id', readonly=False, string='Sales Team (PoS)')
|
||||
pos_down_payment_product_id = fields.Many2one(related='pos_config_id.down_payment_product_id', readonly=False)
|
||||
116
odoo-bringout-oca-ocb-pos_sale/pos_sale/models/sale_order.py
Normal file
116
odoo-bringout-oca-ocb-pos_sale/pos_sale/models/sale_order.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
pos_order_line_ids = fields.One2many('pos.order.line', 'sale_order_origin_id', string="Order lines Transfered to Point of Sale", readonly=True, groups="point_of_sale.group_pos_user")
|
||||
pos_order_count = fields.Integer(string='Pos Order Count', compute='_count_pos_order', readonly=True, groups="point_of_sale.group_pos_user")
|
||||
amount_unpaid = fields.Monetary(string='Unpaid Amount', compute='_compute_amount_unpaid', store=True, help="The amount due from the sale order.")
|
||||
|
||||
def _count_pos_order(self):
|
||||
for order in self:
|
||||
linked_orders = order.pos_order_line_ids.mapped('order_id')
|
||||
order.pos_order_count = len(linked_orders)
|
||||
|
||||
def action_view_pos_order(self):
|
||||
self.ensure_one()
|
||||
linked_orders = self.pos_order_line_ids.mapped('order_id')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Linked POS Orders'),
|
||||
'res_model': 'pos.order',
|
||||
'view_mode': 'tree,form',
|
||||
'domain': [('id', 'in', linked_orders.ids)],
|
||||
}
|
||||
|
||||
@api.depends('order_line', 'amount_total', 'order_line.invoice_lines.parent_state', 'order_line.invoice_lines.price_total', 'order_line.pos_order_line_ids')
|
||||
def _compute_amount_unpaid(self):
|
||||
for sale_order in self:
|
||||
total_invoice_paid = sum(sale_order.order_line.filtered(lambda l: not l.display_type).mapped('invoice_lines').filtered(lambda l: l.parent_state != 'cancel').mapped('price_total'))
|
||||
total_pos_paid = sum(sale_order.order_line.filtered(lambda l: not l.display_type).mapped('pos_order_line_ids.price_subtotal_incl'))
|
||||
sale_order.amount_unpaid = sale_order.amount_total - (total_invoice_paid + total_pos_paid)
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = 'sale.order.line'
|
||||
|
||||
pos_order_line_ids = fields.One2many('pos.order.line', 'sale_order_line_id', string="Order lines Transfered to Point of Sale", readonly=True, groups="point_of_sale.group_pos_user")
|
||||
|
||||
@api.depends('pos_order_line_ids.qty', 'pos_order_line_ids.order_id.picking_ids', 'pos_order_line_ids.order_id.picking_ids.state')
|
||||
def _compute_qty_delivered(self):
|
||||
super()._compute_qty_delivered()
|
||||
for sale_line in self:
|
||||
if all(picking.state == 'done' for picking in sale_line.pos_order_line_ids.order_id.picking_ids):
|
||||
sale_line.qty_delivered += sum((self._convert_qty(sale_line, pos_line.qty, 'p2s') for pos_line in sale_line.pos_order_line_ids if sale_line.product_id.type != 'service'), 0)
|
||||
|
||||
@api.depends('pos_order_line_ids.qty')
|
||||
def _compute_qty_invoiced(self):
|
||||
super()._compute_qty_invoiced()
|
||||
for sale_line in self:
|
||||
sale_line.qty_invoiced += sum([self._convert_qty(sale_line, pos_line.qty, 'p2s') for pos_line in sale_line.pos_order_line_ids], 0)
|
||||
|
||||
def _get_sale_order_fields(self):
|
||||
return ["product_id", "display_name", "price_unit", "product_uom_qty", "tax_id", "qty_delivered", "qty_invoiced", "discount", "qty_to_invoice", "price_total"]
|
||||
|
||||
def read_converted(self):
|
||||
field_names = self._get_sale_order_fields()
|
||||
results = []
|
||||
for sale_line in self:
|
||||
if sale_line.product_type:
|
||||
product_uom = sale_line.product_id.uom_id
|
||||
sale_line_uom = sale_line.product_uom
|
||||
item = sale_line.read(field_names)[0]
|
||||
if sale_line.product_id.tracking != 'none':
|
||||
item['lot_names'] = sale_line.move_ids.move_line_ids.lot_id.mapped('name')
|
||||
if product_uom == sale_line_uom:
|
||||
results.append(item)
|
||||
continue
|
||||
item['product_uom_qty'] = self._convert_qty(sale_line, item['product_uom_qty'], 's2p')
|
||||
item['qty_delivered'] = self._convert_qty(sale_line, item['qty_delivered'], 's2p')
|
||||
item['qty_invoiced'] = self._convert_qty(sale_line, item['qty_invoiced'], 's2p')
|
||||
item['qty_to_invoice'] = self._convert_qty(sale_line, item['qty_to_invoice'], 's2p')
|
||||
item['price_unit'] = sale_line_uom._compute_price(item['price_unit'], product_uom)
|
||||
results.append(item)
|
||||
|
||||
elif sale_line.display_type == 'line_note':
|
||||
if results:
|
||||
if results[-1].get('customer_note'):
|
||||
results[-1]['customer_note'] += "--" + sale_line.name
|
||||
else:
|
||||
results[-1]['customer_note'] = sale_line.name
|
||||
|
||||
|
||||
return results
|
||||
|
||||
@api.model
|
||||
def _convert_qty(self, sale_line, qty, direction):
|
||||
"""Converts the given QTY based on the given SALE_LINE and DIR.
|
||||
|
||||
if DIR='s2p': convert from sale line uom to product uom
|
||||
if DIR='p2s': convert from product uom to sale line uom
|
||||
"""
|
||||
product_uom = sale_line.product_id.uom_id
|
||||
sale_line_uom = sale_line.product_uom
|
||||
if direction == 's2p':
|
||||
return sale_line_uom._compute_quantity(qty, product_uom, False)
|
||||
elif direction == 'p2s':
|
||||
return product_uom._compute_quantity(qty, sale_line_uom, False)
|
||||
|
||||
def unlink(self):
|
||||
# do not delete downpayment lines created from pos
|
||||
pos_downpayment_lines = self.filtered(lambda line: line.is_downpayment and line.sudo().pos_order_line_ids)
|
||||
return super(SaleOrderLine, self - pos_downpayment_lines).unlink()
|
||||
|
||||
@api.depends('pos_order_line_ids')
|
||||
def _compute_untaxed_amount_invoiced(self):
|
||||
super()._compute_untaxed_amount_invoiced()
|
||||
for line in self:
|
||||
line.untaxed_amount_invoiced += sum(line.pos_order_line_ids.mapped('price_subtotal'))
|
||||
|
||||
def _get_downpayment_line_price_unit(self, invoices):
|
||||
return super()._get_downpayment_line_price_unit(invoices) + sum(
|
||||
pol.price_unit for pol in self.pos_order_line_ids
|
||||
)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
def _create_move_from_pos_order_lines(self, lines):
|
||||
lines_to_unreserve = self.env['pos.order.line']
|
||||
for line in lines:
|
||||
if line.order_id.to_ship:
|
||||
continue
|
||||
if any(wh != line.order_id.config_id.warehouse_id for wh in line.sale_order_line_id.move_ids.location_id.warehouse_id):
|
||||
continue
|
||||
lines_to_unreserve |= line
|
||||
lines_to_unreserve.sale_order_line_id.move_ids.filtered(lambda ml: ml.state not in ['cancel', 'done'])._do_unreserve()
|
||||
return super()._create_move_from_pos_order_lines(lines)
|
||||
Loading…
Add table
Add a link
Reference in a new issue