[IMP] point_of_sale: create picking after closing session

When you have a lot of pos order to synchronize with Odoo, it may take a
lot of time because it has to create a picking for each order. So we've
added an option in the settings that will create the picking at the end
of the session.

So the new option by default is creating the picking at session closure,
and if you need a real time stock you can enable the creation of picking
for each order.

closes odoo/odoo#39584

Task-id: 2082064
Signed-off-by: pimodoo <pimodoo@users.noreply.github.com>
This commit is contained in:
Pierre Masereel
2019-11-07 09:34:01 +00:00
parent 293cc0eeab
commit 2f2bde5d9f
20 changed files with 294 additions and 203 deletions
+1
View File
@@ -16,6 +16,7 @@ from . import product
from . import res_partner
from . import res_company
from . import res_config_settings
from . import stock_picking
from . import stock_warehouse
from . import pos_payment
from . import pos_payment_method
+3 -3
View File
@@ -12,9 +12,9 @@ class AccountMove(models.Model):
def _stock_account_get_last_step_stock_moves(self):
stock_moves = super(AccountMove, self)._stock_account_get_last_step_stock_moves()
for invoice in self.filtered(lambda x: x.type == 'out_invoice'):
stock_moves += invoice.sudo().mapped('pos_order_ids.picking_id.move_lines').filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'customer')
stock_moves += invoice.sudo().mapped('pos_order_ids.picking_ids.move_lines').filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'customer')
for invoice in self.filtered(lambda x: x.type == 'out_refund'):
stock_moves += invoice.sudo().mapped('pos_order_ids.picking_id.move_lines').filtered(lambda x: x.state == 'done' and x.location_id.usage == 'customer')
stock_moves += invoice.sudo().mapped('pos_order_ids.picking_ids.move_lines').filtered(lambda x: x.state == 'done' and x.location_id.usage == 'customer')
return stock_moves
class AccountMoveLine(models.Model):
@@ -27,5 +27,5 @@ class AccountMoveLine(models.Model):
price_unit = super(AccountMoveLine, self)._stock_account_get_anglo_saxon_price_unit()
order = self.move_id.pos_order_ids
if order:
price_unit = - order._get_pos_anglo_saxon_price_unit(self.product_id, self.move_id.partner_id.id, self.quantity)
price_unit = order._get_pos_anglo_saxon_price_unit(self.product_id, self.move_id.partner_id.id, self.quantity)
return price_unit
+3 -3
View File
@@ -107,9 +107,9 @@ class PosConfig(models.Model):
'stock.picking.type',
string='Operation Type',
default=_default_picking_type_id,
required=True,
domain="[('code', '=', 'outgoing'), ('warehouse_id.company_id', '=', company_id)]",
ondelete='restrict')
use_existing_lots = fields.Boolean(related='picking_type_id.use_existing_lots', readonly=False)
journal_id = fields.Many2one(
'account.journal', string='Sales Journal',
domain=[('type', '=', 'sale')],
@@ -510,10 +510,10 @@ class PosConfig(models.Model):
# Methods to open the POS
def open_ui(self):
"""Open the pos interface with config_id as an extra argument.
In vanilla PoS each user can only have one active session, therefore it was not needed to pass the config_id
on opening a session. It is also possible to login to sessions created by other users.
:returns: dict
"""
self.ensure_one()
+33 -159
View File
@@ -137,10 +137,10 @@ class PosOrder(models.Model):
except Exception as e:
_logger.error('Could not fully process the POS Order: %s', tools.ustr(e))
pos_order._create_order_picking()
if pos_order.to_invoice and pos_order.state == 'paid':
pos_order.action_pos_order_invoice()
pos_order.account_move.sudo().with_context(force_company=self.env.user.company_id.id).post()
return pos_order.id
@@ -193,11 +193,11 @@ class PosOrder(models.Model):
def _get_pos_anglo_saxon_price_unit(self, product, partner_id, quantity):
moves = self.filtered(lambda o: o.partner_id.id == partner_id)\
.mapped('picking_id.move_lines')\
.mapped('picking_ids.move_lines')\
.filtered(lambda m: m.product_id.id == product.id)\
.sorted(lambda x: x.date)
price_unit = product._compute_average_price(0, quantity, moves)
return - price_unit
return price_unit
name = fields.Char(string='Order Ref', required=True, readonly=True, copy=False, default='/')
date_order = fields.Datetime(string='Date', readonly=True, index=True, default=fields.Datetime.now)
@@ -233,14 +233,11 @@ class PosOrder(models.Model):
'Status', readonly=True, copy=False, default='draft')
account_move = fields.Many2one('account.move', string='Invoice', readonly=True, copy=False)
picking_id = fields.Many2one('stock.picking', string='Picking', readonly=True, copy=False)
picking_ids = fields.One2many('stock.picking', 'pos_order_id')
picking_count = fields.Integer(compute='_compute_picking_count')
failed_pickings = fields.Boolean(compute='_compute_picking_count')
picking_type_id = fields.Many2one('stock.picking.type', related='session_id.config_id.picking_type_id', string="Operation Type", readonly=False)
location_id = fields.Many2one(
comodel_name='stock.location',
related='picking_id.location_id',
string="Location", store=True,
readonly=True,
)
note = fields.Text(string='Internal Notes')
nb_print = fields.Integer(string='Number of Print', readonly=True, copy=False, default=0)
pos_reference = fields.Char(string='Receipt Number', readonly=True, copy=False)
@@ -261,6 +258,11 @@ class PosOrder(models.Model):
for order in self:
order.is_invoiced = bool(order.account_move)
@api.depends('picking_ids', 'picking_ids.state')
def _compute_picking_count(self):
for order in self:
order.picking_count = len(order.picking_ids)
order.failed_pickings = bool(order.picking_ids.filtered(lambda p: p.state != 'done'))
@api.depends('date_order', 'company_id', 'currency_id', 'company_id.currency_id')
def _compute_currency_rate(self):
@@ -333,6 +335,14 @@ class PosOrder(models.Model):
vals['name'] = order.config_id.sequence_id._next()
return super(PosOrder, self).write(vals)
def action_stock_picking(self):
self.ensure_one()
action_picking = self.env.ref('stock.action_picking_tree_ready')
action = action_picking.read()[0]
action['context'] = {}
action['domain'] = [('id', 'in', self.picking_ids.ids)]
return action
def action_view_invoice(self):
return {
'name': _('Customer Invoice'),
@@ -345,10 +355,11 @@ class PosOrder(models.Model):
}
def action_pos_order_paid(self):
self.ensure_one()
if not float_is_zero(self.amount_total - self.amount_paid, precision_rounding=self.currency_id.rounding):
raise UserError(_("Order %s is not fully paid.") % self.name)
self.write({'state': 'paid'})
return self.create_picking()
return True
def action_pos_order_invoice(self):
moves = self.env['account.move']
@@ -427,156 +438,19 @@ class PosOrder(models.Model):
return self.env['pos.order'].search_read(domain = [('id', 'in', order_ids)], fields = ['id', 'pos_reference'])
def create_picking(self):
"""Create a picking for each order and validate it."""
Picking = self.env['stock.picking']
# If no email is set on the user, the picking creation and validation will fail be cause of
# the 'Unable to log message, please configure the sender's email address.' error.
# We disable the tracking in this case.
if not self.env.user.partner_id.email:
Picking = Picking.with_context(tracking_disable=True)
Move = self.env['stock.move']
StockWarehouse = self.env['stock.warehouse']
for order in self:
if not order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu']):
continue
address = order.partner_id.address_get(['delivery']) or {}
picking_type = order.picking_type_id
return_pick_type = order.picking_type_id.return_picking_type_id or order.picking_type_id
order_picking = Picking
return_picking = Picking
moves = Move
location_id = picking_type.default_location_src_id.id
if order.partner_id:
destination_id = order.partner_id.property_stock_customer.id
else:
if (not picking_type) or (not picking_type.default_location_dest_id):
customerloc, supplierloc = StockWarehouse._get_partner_locations()
destination_id = customerloc.id
else:
destination_id = picking_type.default_location_dest_id.id
if picking_type:
message = _("This transfer has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (order.id, order.name)
picking_vals = {
'origin': order.name,
'partner_id': address.get('delivery', False),
'user_id': False,
'date_done': order.date_order,
'picking_type_id': picking_type.id,
'company_id': order.company_id.id,
'move_type': 'direct',
'note': order.note or "",
'location_id': location_id,
'location_dest_id': destination_id,
}
pos_qty = any([x.qty > 0 for x in order.lines if x.product_id.type in ['product', 'consu']])
if pos_qty:
order_picking = Picking.create(picking_vals.copy())
if self.env.user.partner_id.email:
order_picking.message_post(body=message)
else:
order_picking.sudo().message_post(body=message)
neg_qty = any([x.qty < 0 for x in order.lines if x.product_id.type in ['product', 'consu']])
if neg_qty:
return_vals = picking_vals.copy()
return_vals.update({
'location_id': destination_id,
'location_dest_id': return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or location_id,
'picking_type_id': return_pick_type.id
})
return_picking = Picking.create(return_vals)
if self.env.user.partner_id.email:
return_picking.message_post(body=message)
else:
return_picking.message_post(body=message)
for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu'] and not float_is_zero(l.qty, precision_rounding=l.product_id.uom_id.rounding)):
moves |= Move.create({
'name': line.name,
'product_uom': line.product_id.uom_id.id,
'picking_id': order_picking.id if line.qty >= 0 else return_picking.id,
'picking_type_id': picking_type.id if line.qty >= 0 else return_pick_type.id,
'product_id': line.product_id.id,
'product_uom_qty': abs(line.qty),
'state': 'draft',
'location_id': location_id if line.qty >= 0 else destination_id,
'location_dest_id': destination_id if line.qty >= 0 else return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or location_id,
})
# prefer associating the regular order picking, not the return
order.write({'picking_id': order_picking.id or return_picking.id})
if return_picking:
order._force_picking_done(return_picking)
if order_picking:
order._force_picking_done(order_picking)
# when the pos.config has no picking_type_id set only the moves will be created
if moves and not return_picking and not order_picking:
moves._action_assign()
moves.filtered(lambda m: m.product_id.tracking == 'none')._action_done()
return True
def _force_picking_done(self, picking):
"""Force picking in order to be set as done."""
def _create_order_picking(self):
self.ensure_one()
picking.action_assign()
wrong_lots = self.set_pack_operation_lot(picking)
if not wrong_lots:
picking._action_done()
if not self.session_id.update_stock_at_closing or (self.company_id.anglo_saxon_accounting and self.to_invoice):
picking_type = self.config_id.picking_type_id
if self.partner_id.property_stock_customer:
destination_id = self.partner_id.property_stock_customer.id
elif not picking_type or not picking_type.default_location_dest_id:
destination_id = self.env['stock.warehouse']._get_partner_locations()[0].id
else:
destination_id = picking_type.default_location_dest_id.id
def set_pack_operation_lot(self, picking=None):
"""Set Serial/Lot number in pack operations to mark the pack operation done."""
StockProductionLot = self.env['stock.production.lot']
PosPackOperationLot = self.env['pos.pack.operation.lot']
has_wrong_lots = False
for order in self:
for move in (picking or self.picking_id).move_lines:
picking_type = (picking or self.picking_id).picking_type_id
lots_necessary = True
if picking_type:
lots_necessary = picking_type and picking_type.use_existing_lots
qty_done = 0
pack_lots = []
pos_pack_lots = PosPackOperationLot.search([('order_id', '=', order.id), ('product_id', '=', move.product_id.id)])
if pos_pack_lots and lots_necessary:
for pos_pack_lot in pos_pack_lots:
stock_production_lot = StockProductionLot.search([('name', '=', pos_pack_lot.lot_name), ('product_id', '=', move.product_id.id)])
if stock_production_lot:
# a serialnumber always has a quantity of 1 product, a lot number takes the full quantity of the order line
qty = 1.0
if stock_production_lot.product_id.tracking == 'lot':
qty = abs(pos_pack_lot.pos_order_line_id.qty)
qty_done += qty
pack_lots.append({'lot_id': stock_production_lot.id, 'qty': qty})
else:
has_wrong_lots = True
elif move.product_id.tracking == 'none' or not lots_necessary:
qty_done = move.product_uom_qty
else:
has_wrong_lots = True
for pack_lot in pack_lots:
lot_id, qty = pack_lot['lot_id'], pack_lot['qty']
self.env['stock.move.line'].create({
'picking_id': move.picking_id.id,
'move_id': move.id,
'product_id': move.product_id.id,
'product_uom_id': move.product_uom.id,
'qty_done': qty,
'location_id': move.location_id.id,
'location_dest_id': move.location_dest_id.id,
'lot_id': lot_id,
})
if not pack_lots and not float_is_zero(qty_done, precision_rounding=move.product_uom.rounding):
if len(move._get_move_lines()) < 2:
move.quantity_done = qty_done
else:
move._set_quantity_done(qty_done)
return has_wrong_lots
pickings = self.env['stock.picking']._create_picking_from_pos_order_lines(destination_id, self.lines, picking_type, self.partner_id)
pickings.write({'pos_session_id': self.session_id.id, 'pos_order_id': self.id, 'origin': self.name})
def add_payment(self, data):
"""Create a new payment for the order"""
+57 -7
View File
@@ -84,7 +84,9 @@ class PosSession(models.Model):
order_ids = fields.One2many('pos.order', 'session_id', string='Orders')
order_count = fields.Integer(compute='_compute_order_count')
statement_ids = fields.One2many('account.bank.statement', 'pos_session_id', string='Cash Statements', readonly=True)
failed_pickings = fields.Boolean(compute='_compute_picking_count')
picking_count = fields.Integer(compute='_compute_picking_count')
picking_ids = fields.One2many('stock.picking', 'pos_session_id')
rescue = fields.Boolean(string='Recovery Session',
help="Auto-generated session for orphan orders, ignored in constraints",
readonly=True,
@@ -93,6 +95,7 @@ class PosSession(models.Model):
payment_method_ids = fields.Many2many('pos.payment.method', related='config_id.payment_method_ids', string='Payment Methods')
total_payments_amount = fields.Float(compute='_compute_total_payments_amount', string='Total Payments Amount')
is_in_company_currency = fields.Boolean('Is Using Company Currency', compute='_compute_is_in_company_currency')
update_stock_at_closing = fields.Boolean('Stock should be updated at closing')
_sql_constraints = [('uniq_name', 'unique(name)', "The name of this POS Session must be unique !")]
@@ -128,17 +131,18 @@ class PosSession(models.Model):
for session in self:
session.order_count = sessions_data.get(session.id, 0)
@api.depends('picking_ids', 'picking_ids.state')
def _compute_picking_count(self):
for pos in self:
pickings = pos.order_ids.mapped('picking_id').filtered(lambda x: x.state != 'done')
pos.picking_count = len(pickings.ids)
for session in self:
session.picking_count = len(session.picking_ids.ids)
session.failed_pickings = bool(session.picking_ids.filtered(lambda p: p.state != 'done'))
def action_stock_picking(self):
pickings = self.order_ids.mapped('picking_id').filtered(lambda x: x.state != 'done')
self.ensure_one()
action_picking = self.env.ref('stock.action_picking_tree_ready')
action = action_picking.read()[0]
action['context'] = {}
action['domain'] = [('id', 'in', pickings.ids)]
action['domain'] = [('id', 'in', self.picking_ids.ids)]
return action
@api.depends('config_id', 'statement_ids', 'payment_method_ids')
@@ -204,10 +208,13 @@ class PosSession(models.Model):
}
statement_ids |= statement_ids.with_context(ctx).create(st_values)
update_stock_at_closing = pos_config.company_id.point_of_sale_update_stock_quantities == "closing"
values.update({
'name': pos_name,
'statement_ids': [(6, 0, statement_ids.ids)],
'config_id': config_id,
'update_stock_at_closing': update_stock_at_closing,
})
if self.user_has_groups('point_of_sale.group_pos_user'):
@@ -283,6 +290,8 @@ class PosSession(models.Model):
def _validate_session(self):
self.ensure_one()
self._check_if_no_draft_orders()
if self.update_stock_at_closing:
self._create_picking_at_end_of_session()
self._create_account_move()
if self.move_id.line_ids:
self.move_id.post()
@@ -302,6 +311,30 @@ class PosSession(models.Model):
'params': {'menu_id': self.env.ref('point_of_sale.menu_point_root').id},
}
def _create_picking_at_end_of_session(self):
self.ensure_one()
lines_grouped_by_dest_location = {}
picking_type = self.config_id.picking_type_id
if not picking_type or not picking_type.default_location_dest_id:
session_destination_id = self.env['stock.warehouse']._get_partner_locations()[0].id
else:
session_destination_id = picking_type.default_location_dest_id.id
for order in self.order_ids:
if order.company_id.anglo_saxon_accounting and order.to_invoice:
continue
destination_id = order.partner_id.property_stock_customer.id or session_destination_id
if destination_id in lines_grouped_by_dest_location:
lines_grouped_by_dest_location[destination_id] |= order.lines
else:
lines_grouped_by_dest_location[destination_id] = order.lines
for location_dest_id, lines in lines_grouped_by_dest_location.items():
pickings = self.env['stock.picking']._create_picking_from_pos_order_lines(location_dest_id, lines, picking_type)
pickings.write({'pos_session_id': self.id, 'origin': self.name})
def _create_account_move(self):
""" Create account.move and account.move.line records for this session.
@@ -397,7 +430,7 @@ class PosSession(models.Model):
if self.company_id.anglo_saxon_accounting:
# Combine stock lines
stock_moves = self.env['stock.move'].search([
('picking_id', '=', order.picking_id.id),
('picking_id', 'in', order.picking_ids.ids),
('company_id.anglo_saxon_accounting', '=', True),
('product_id.categ_id.property_valuation', '=', 'real_time')
])
@@ -408,6 +441,21 @@ class PosSession(models.Model):
stock_expense[exp_key] = self._update_amounts(stock_expense[exp_key], {'amount': amount}, move.picking_id.date)
stock_output[out_key] = self._update_amounts(stock_output[out_key], {'amount': amount}, move.picking_id.date)
if self.company_id.anglo_saxon_accounting:
global_session_pickings = self.picking_ids.filtered(lambda p: not p.pos_order_id)
if global_session_pickings:
stock_moves = self.env['stock.move'].search([
('picking_id', 'in', global_session_pickings.ids),
('company_id.anglo_saxon_accounting', '=', True),
('product_id.categ_id.property_valuation', '=', 'real_time'),
])
for move in stock_moves:
exp_key = move.product_id.property_account_expense_id or move.product_id.categ_id.property_account_expense_categ_id
out_key = move.product_id.categ_id.property_stock_account_output_categ_id
amount = -sum(move.stock_valuation_layer_ids.mapped('value'))
stock_expense[exp_key] = self._update_amounts(stock_expense[exp_key], {'amount': amount}, move.picking_id.date)
stock_output[out_key] = self._update_amounts(stock_output[out_key], {'amount': amount}, move.picking_id.date)
## SECTION: Create non-reconcilable move lines
# Create account.move.line records for
# - sales
@@ -514,7 +562,9 @@ class PosSession(models.Model):
).reconcile()
# reconcile stock output lines
stock_moves = self.env['stock.move'].search([('picking_id', 'in', self.order_ids.filtered(lambda order: not order.is_invoiced).mapped('picking_id').ids)])
pickings = self.picking_ids.filtered(lambda p: not p.pos_order_id)
pickings |= self.order_ids.filtered(lambda o: not o.is_invoiced).mapped('picking_ids')
stock_moves = self.env['stock.move'].search([('picking_id', 'in', pickings.ids)])
stock_account_move_lines = self.env['account.move'].search([('stock_move_id', 'in', stock_moves.ids)]).mapped('line_ids')
for account_id in stock_output_lines:
( stock_output_lines[account_id]
@@ -6,6 +6,12 @@ from odoo.exceptions import ValidationError
class ResCompany(models.Model):
_inherit = 'res.company'
point_of_sale_update_stock_quantities = fields.Selection([
('closing', 'At the session closing (advised)'),
('real', 'In real time'),
], default='closing', string="Update quantities in stock",
help="At the session closing: A picking is created for the entire session when it's closed\n In real time: Each order sent to the server create its own picking")
@api.constrains('period_lock_date', 'fiscalyear_lock_date')
def validate_period_lock_date(self):
""" This constrains makes it impossible to change the period lock date if
@@ -9,3 +9,4 @@ class ResConfigSettings(models.TransientModel):
sale_tax_id = fields.Many2one('account.tax', string="Default Sale Tax", related='company_id.account_sale_tax_id', readonly=False)
module_pos_mercury = fields.Boolean(string="Vantiv Payment Terminal", help="The transactions are processed by Vantiv. Set your Vantiv credentials on the related payment method.")
module_pos_adyen = fields.Boolean(string="Adyen Payment Terminal", help="The transactions are processed by Adyen. Set your Adyen credentials on the related payment method.")
update_stock_quantities = fields.Selection(related="company_id.point_of_sale_update_stock_quantities", readonly=False)
@@ -0,0 +1,133 @@
# -*- 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 odoo.tools import float_is_zero
from itertools import groupby
class StockPicking(models.Model):
_inherit='stock.picking'
pos_session_id = fields.Many2one('pos.session')
pos_order_id = fields.Many2one('pos.order')
@api.model
def _create_picking_from_pos_order_lines(self, location_dest_id, lines, picking_type, partner=False):
"""We'll create some picking based on order_lines"""
pickings = self.env['stock.picking']
stockable_lines = lines.filtered(lambda l: l.product_id.type in ['product', 'consu'] and not float_is_zero(l.qty, precision_rounding=l.product_id.uom_id.rounding))
if not stockable_lines:
return pickings
positive_lines = stockable_lines.filtered(lambda l: l.qty > 0)
negative_lines = stockable_lines - positive_lines
if positive_lines:
location_id = picking_type.default_location_src_id.id
positive_picking = self.env['stock.picking'].create({
'partner_id': partner.id if partner else False,
'user_id': False,
'picking_type_id': picking_type.id,
'move_type': 'direct',
'location_id': location_id,
'location_dest_id': location_dest_id,
})
positive_picking._create_move_from_pos_order_lines(positive_lines)
try:
with self.env.cr.savepoint():
positive_picking._action_done()
except (UserError, ValidationError):
pass
pickings |= positive_picking
if negative_lines:
if picking_type.return_picking_type_id:
return_picking_type = picking_type.return_picking_type_id
return_location_id = return_picking_type.default_location_dest_id.id
else:
return_picking_type = picking_type
return_location_id = picking_type.default_location_src_id.id
negative_picking = self.env['stock.picking'].create({
'partner_id': partner.id if partner else False,
'user_id': False,
'picking_type_id': return_picking_type.id,
'move_type': 'direct',
'location_id': location_dest_id,
'location_dest_id': return_location_id,
})
negative_picking._create_move_from_pos_order_lines(negative_lines)
try:
with self.env.cr.savepoint():
negative_picking._action_done()
except (UserError, ValidationError):
pass
pickings |= negative_picking
return pickings
def _create_move_from_pos_order_lines(self, lines):
self.ensure_one()
lines_by_product = groupby(sorted(lines, key=lambda l: l.product_id.id), key=lambda l: l.product_id.id)
for product, lines in lines_by_product:
order_lines = self.env['pos.order.line'].concat(*lines)
first_line = order_lines[0]
current_move = self.env['stock.move'].create({
'name': first_line.name,
'product_uom': first_line.product_id.uom_id.id,
'picking_id': self.id,
'picking_type_id': self.picking_type_id.id,
'product_id': first_line.product_id.id,
'product_uom_qty': abs(sum(order_lines.mapped('qty'))),
'state': 'draft',
'location_id': self.location_id.id,
'location_dest_id': self.location_dest_id.id,
'company_id': self.company_id.id,
})
if first_line.product_id.tracking != 'none' and (self.picking_type_id.use_existing_lots or self.picking_type_id.use_create_lots):
for line in order_lines:
sum_of_lots = 0
for lot in line.pack_lot_ids.filtered(lambda l: l.lot_name):
if line.product_id.tracking == 'serial':
qty = 1
else:
qty = abs(line.qty)
ml_vals = current_move._prepare_move_line_vals()
ml_vals.update({'qty_done':qty})
if self.picking_type_id.use_existing_lots:
existing_lot = self.env['stock.production.lot'].search([
('company_id', '=', self.company_id.id),
('product_id', '=', line.product_id.id),
('name', '=', lot.lot_name)
])
if not existing_lot and self.picking_type_id.use_create_lots:
existing_lot = self.env['stock.production.lot'].create({
'company_id': self.company_id.id,
'product_id': line.product_id.id,
'name': lot.lot_name,
})
ml_vals.update({
'lot_id': existing_lot.id,
})
else:
ml_vals.update({
'lot_name': lot.lot_name,
})
self.env['stock.move.line'].create(ml_vals)
sum_of_lots += qty
if abs(line.qty) != sum_of_lots:
difference_qty = abs(line.qty) - sum_of_lots
ml_vals = current_move._prepare_move_line_vals()
if line.product_id.tracking == 'serial':
ml_vals.update({'qty_done': 1})
for i in range(int(difference_qty)):
self.env['stock.move.line'].create(ml_vals)
else:
ml_vals.update({'qty_done': difference_qty})
self.env['stock.move.line'].create(ml_vals)
else:
current_move.quantity_done = abs(sum(order_lines.mapped('qty')))
@@ -38,6 +38,7 @@ class Warehouse(models.Model):
'sequence': max_sequence + 1,
'sequence_code': 'POS',
'company_id': self.company_id.id,
'show_operations': False,
}
})
return picking_type_create_values, max_sequence + 2
@@ -24,7 +24,6 @@ class PosOrderReport(models.Model):
price_sub_total = fields.Float(string='Subtotal w/o discount', readonly=True)
total_discount = fields.Float(string='Total Discount', readonly=True)
average_price = fields.Float(string='Average Price', readonly=True, group_operator="avg")
location_id = fields.Many2one('stock.location', string='Location', readonly=True)
company_id = fields.Many2one('res.company', string='Company', readonly=True)
nbr_lines = fields.Integer(string='Sale Line Count', readonly=True)
product_qty = fields.Integer(string='Product Quantity', readonly=True)
@@ -53,7 +52,6 @@ class PosOrderReport(models.Model):
s.partner_id AS partner_id,
s.state AS state,
s.user_id AS user_id,
s.location_id AS location_id,
s.company_id AS company_id,
s.sale_journal AS journal_id,
l.product_id AS product_id,
@@ -80,7 +78,7 @@ class PosOrderReport(models.Model):
return """
GROUP BY
s.id, s.date_order, s.partner_id,s.state, pt.categ_id,
s.user_id, s.location_id, s.company_id, s.sale_journal,
s.user_id, s.company_id, s.sale_journal,
s.pricelist_id, s.account_move, s.create_date, s.session_id,
l.product_id,
pt.categ_id, pt.pos_categ_id,
+1 -1
View File
@@ -1483,7 +1483,7 @@ exports.Orderline = Backbone.Model.extend({
return orderline;
},
set_product_lot: function(product){
this.has_product_lot = product.tracking !== 'none' && this.pos.config.use_existing_lots;
this.has_product_lot = product.tracking !== 'none';
this.pack_lot_lines = this.has_product_lot && new PacklotlineCollection(null, {'order_line': this});
},
// sets a discount [0,100]%
+4 -2
View File
@@ -19,6 +19,7 @@ class TestPointOfSaleCommon(StockAccountMinimalTest):
cls.PosOrder = cls.env['pos.order']
cls.PosSession = cls.env['pos.session']
cls.company = cls.env.ref('base.main_company')
cls.company.point_of_sale_update_stock_quantities = 'real'
cls.company_id = cls.company.id
coa = cls.env['account.chart.template'].search([
('currency_id', '=', cls.company.currency_id.id),
@@ -162,6 +163,7 @@ class TestPoSCommon(StockAccountMinimalTest):
# Set basic defaults
cls.company = cls.env.ref('base.main_company')
cls.company.point_of_sale_update_stock_quantities = 'real'
cls.pos_sale_journal = cls.env['account.journal'].create({
'type': 'sale',
'name': 'Point of Sale Test',
@@ -183,8 +185,8 @@ class TestPoSCommon(StockAccountMinimalTest):
'name': 'Debtors - (POS)',
'reconcile': True,
'user_type_id': cls.env.ref('account.data_account_type_receivable').id,
})
cls.pos_receivable_account = cls.company.account_default_pos_receivable_account_id
})
cls.pos_receivable_account = cls.company.account_default_pos_receivable_account_id
cls.other_receivable_account = cls.env['account.account'].create({
'name': 'Other Receivable',
'code': 'RCV00' ,
@@ -42,6 +42,7 @@ class TestAngloSaxonCommon(common.TransactionCase):
'type': 'product',
})
self.company.anglo_saxon_accounting = True
self.company.point_of_sale_update_stock_quantities = 'real'
self.product.categ_id = self.category
self.product.property_account_expense_id = account_expense
self.product.property_account_income_id = account_income
@@ -155,12 +155,12 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
# I test that the pickings are created as expected during payment
# One picking attached and having all the positive move lines in the correct state
self.assertEqual(
self.pos_order_pos1.picking_id.state,
self.pos_order_pos1.picking_ids[0].state,
'done',
'Picking should be in done state.'
)
self.assertEqual(
self.pos_order_pos1.picking_id.move_lines.mapped('state'),
self.pos_order_pos1.picking_ids[0].move_lines.mapped('state'),
['done', 'done'],
'Move Lines should be in done state.'
)
@@ -219,14 +219,13 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
# I test that the pickings are created as expected
# One picking attached and having all the positive move lines in the correct state
self.pos_order_pos2.create_picking()
self.assertEqual(
self.pos_order_pos2.picking_id.state,
self.pos_order_pos2.picking_ids[0].state,
'done',
'Picking should be in done state.'
)
self.assertEqual(
self.pos_order_pos2.picking_id.move_lines.mapped('state'),
self.pos_order_pos2.picking_ids[0].move_lines.mapped('state'),
['done', 'done'],
'Move Lines should be in done state.'
)
@@ -285,12 +284,12 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
# I test that the pickings are created as expected
# One picking attached and having all the positive move lines in the correct state
self.assertEqual(
self.pos_order_pos3.picking_id.state,
self.pos_order_pos3.picking_ids[0].state,
'done',
'Picking should be in done state.'
)
self.assertEqual(
self.pos_order_pos3.picking_id.move_lines.mapped('state'),
self.pos_order_pos3.picking_ids[0].move_lines.mapped('state'),
['done'],
'Move Lines should be in done state.'
)
@@ -100,11 +100,11 @@ class TestPoSBasicConfig(TestPoSCommon):
# picking and stock moves should be in done state
for order in self.pos_session.order_ids:
self.assertEqual(
order.picking_id.state,
order.picking_ids[0].state,
'done',
'Picking should be in done state.'
)
move_lines = order.picking_id.move_lines
move_lines = order.picking_ids[0].move_lines
self.assertEqual(
move_lines.mapped('state'),
['done'] * len(move_lines),
@@ -216,11 +216,11 @@ class TestPoSBasicConfig(TestPoSCommon):
# no exception for invoiced orders
for order in self.pos_session.order_ids:
self.assertEqual(
order.picking_id.state,
order.picking_ids[0].state,
'done',
'Picking should be in done state.'
)
move_lines = order.picking_id.move_lines
move_lines = order.picking_ids[0].move_lines
self.assertEqual(
move_lines.mapped('state'),
['done'] * len(move_lines),
@@ -376,11 +376,11 @@ class TestPoSBasicConfig(TestPoSCommon):
# no exception of return orders
for order in self.pos_session.order_ids:
self.assertEqual(
order.picking_id.state,
order.picking_ids[0].state,
'done',
'Picking should be in done state.'
)
move_lines = order.picking_id.move_lines
move_lines = order.picking_ids[0].move_lines
self.assertEqual(
move_lines.mapped('state'),
['done'] * len(move_lines),
@@ -440,4 +440,4 @@ class TestPoSBasicConfig(TestPoSCommon):
self.assertAlmostEqual(sum(cash_receivable_lines.mapped('balance')), 290, msg='Total cash receivable balance should be equal to the total cash payments.')
for line in cash_receivable_lines:
self.assertTrue(line.full_reconcile_id, msg='Each cash receivable line should be fully-reconciled.')
self.assertTrue(line.full_reconcile_id, msg='Each cash receivable line should be fully-reconciled.')
@@ -91,8 +91,8 @@ class TestPoSStock(TestPoSCommon):
# picking and stock moves should be in done state
for order in self.pos_session.order_ids:
self.assertEqual(order.picking_id.state, 'done', 'Picking should be in done state.')
self.assertTrue(all(state == 'done' for state in order.picking_id.move_lines.mapped('state')), 'Move Lines should be in done state.' )
self.assertEqual(order.picking_ids[0].state, 'done', 'Picking should be in done state.')
self.assertTrue(all(state == 'done' for state in order.picking_ids[0].move_lines.mapped('state')), 'Move Lines should be in done state.' )
# close the session
self.pos_session.action_pos_session_validate()
@@ -166,8 +166,8 @@ class TestPoSStock(TestPoSCommon):
# picking and stock moves should be in done state
for order in self.pos_session.order_ids:
self.assertEqual(order.picking_id.state, 'done', 'Picking should be in done state.')
self.assertTrue(all(state == 'done' for state in order.picking_id.move_lines.mapped('state')), 'Move Lines should be in done state.' )
self.assertEqual(order.picking_ids[0].state, 'done', 'Picking should be in done state.')
self.assertTrue(all(state == 'done' for state in order.picking_ids[0].move_lines.mapped('state')), 'Move Lines should be in done state.' )
# close the session
self.pos_session.action_pos_session_validate()
@@ -14,7 +14,16 @@
<field name="state" widget="statusbar" statusbar_visible="draft,paid,done" />
</header>
<sheet>
<field name="failed_pickings" invisible="1"/>
<div class="oe_button_box" name="button_box">
<button name="action_stock_picking"
type="object"
class="oe_stat_button"
icon="fa-truck"
attrs="{'invisible':[('picking_count', '=', 0)]}">
<field name="picking_count" widget="statinfo" string="Pickings" attrs="{'invisible': [('failed_pickings', '!=', False)]}"/>
<field name="picking_count" widget="statinfo" string="Pickings" class="text-danger" attrs="{'invisible': [('failed_pickings', '=', False)]}"/>
</button>
<button name="action_view_invoice"
string="Invoice"
type="object"
@@ -90,10 +99,6 @@
</page>
<page name="extra" string="Extra Info">
<group >
<group string="Inventory">
<field name="location_id" widget="selection" groups="stock.group_stock_multi_locations"/>
<field name="picking_id" readonly="1"/>
</group>
<group
string="Accounting"
groups="account.group_account_manager"
@@ -26,6 +26,7 @@
</header>
<sheet>
<field name="cash_register_id" invisible="1"/>
<field name="failed_pickings" invisible="1"/>
<div class="oe_button_box" name="button_box">
<button name="action_view_order"
class="oe_stat_button"
@@ -33,8 +34,9 @@
type="object">
<field name="order_count" widget="statinfo" string="Orders"/>
</button>
<button class="oe_stat_button" name="action_stock_picking" type="object" icon="fa-arrows-v" attrs="{'invisible':[('picking_count', '=', 0)]}">
<field name="picking_count" widget="statinfo" string="Picking Errors"/>
<button class="oe_stat_button" name="action_stock_picking" type="object" icon="fa-truck" attrs="{'invisible':[('picking_count', '=', 0)]}">
<field name="picking_count" widget="statinfo" string="Pickings" attrs="{'invisible': [('failed_pickings', '!=', False)]}"/>
<field name="picking_count" widget="statinfo" string="Pickings" class="text-danger" attrs="{'invisible': [('failed_pickings', '=', False)]}"/>
</button>
<button
name="action_show_payments_list"
@@ -180,7 +182,7 @@
<field name="config_id" />
<field name="user_id" />
<filter name="my_sessions" string="My Sessions" domain="[('user_id', '=', uid)]"/>
<separator/>
<separator/>
<filter name="open_sessions" string="In Progress" domain="[('state', '=', 'opened')]"/>
<separator/>
<filter string="Opening Date" name="start_date" date="start_at" />
@@ -8,6 +8,23 @@
<field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside">
<div class="app_settings_block" data-string="Point of sale" string="Point of Sale" data-key="point_of_sale" groups="point_of_sale.group_pos_manager">
<h2>Product</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<div>
<label string="Inventory Management" for="update_stock_quantities"/>
<span class="fa fa-lg fa-building-o" title="Values set here are company-specific." aria-label="Values set here are company-specific." groups="base.group_multi_company" role="img"/>
<div class="text-muted">
Update quantities in stock
</div>
<div class="content-group mt16">
<field name="update_stock_quantities" colspan="4" nolabel="1" widget="radio"/>
</div>
</div>
</div>
</div>
</div>
<h2>Taxes</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box" title="This tax is applied to any new product created in the catalog.">
@@ -56,6 +56,7 @@ class PosMakePayment(models.TransientModel):
if float_is_zero(order.amount_total - order.amount_paid, precision_rounding=currency.rounding):
order.action_pos_order_paid()
order._create_order_picking()
return {'type': 'ir.actions.act_window_close'}
return self.launch_payment()