Files
tryton/sale.py
T

1681 lines
61 KiB
Python
Raw Normal View History

2014-12-02 12:55:52 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2010-08-10 11:58:48 -04:00
from decimal import Decimal
2013-01-06 22:18:13 +01:00
from itertools import groupby, chain
2011-09-29 12:38:21 +02:00
from functools import partial
from sql import Table, Null
2013-09-06 22:34:40 +02:00
from sql.functions import Overlay, Position
from sql.operators import Concat
2012-03-25 00:18:13 +01:00
from trytond.model import Workflow, ModelView, ModelSQL, fields
2009-10-02 15:11:51 +02:00
from trytond.modules.company import CompanyReport
2012-01-02 18:04:22 +01:00
from trytond.wizard import Wizard, StateAction, StateView, StateTransition, \
Button
2013-09-06 22:34:40 +02:00
from trytond import backend
2012-09-10 15:28:33 +02:00
from trytond.pyson import If, Eval, Bool, PYSONEncoder, Id
2010-08-10 11:58:48 -04:00
from trytond.transaction import Transaction
2012-09-24 22:18:13 +02:00
from trytond.pool import Pool, PoolMeta
2011-07-03 13:19:11 +02:00
__all__ = ['Sale', 'SaleIgnoredInvoice', 'SaleRecreatedInvoice',
'SaleLine', 'SaleLineTax', 'SaleLineIgnoredMove',
'SaleLineRecreatedMove', 'SaleReport', 'OpenCustomer',
2012-09-24 22:18:13 +02:00
'HandleShipmentExceptionAsk', 'HandleShipmentException',
'HandleInvoiceExceptionAsk', 'HandleInvoiceException',
'ReturnSaleStart', 'ReturnSale']
2012-09-24 22:18:13 +02:00
__metaclass__ = PoolMeta
2013-06-01 00:01:39 +02:00
_ZERO = Decimal(0)
2008-09-03 21:28:12 +02:00
2012-03-25 00:18:13 +01:00
class Sale(Workflow, ModelSQL, ModelView):
2008-09-03 21:28:12 +02:00
'Sale'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.sale'
2008-09-03 21:28:12 +02:00
_rec_name = 'reference'
company = fields.Many2One('company.company', 'Company', required=True,
2011-08-04 16:57:40 +02:00
states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
2011-08-04 16:57:40 +02:00
},
domain=[
2011-08-21 11:20:51 +02:00
('id', If(Eval('context', {}).contains('company'), '=', '!='),
2013-10-09 18:25:47 +02:00
Eval('context', {}).get('company', -1)),
2011-08-04 16:57:40 +02:00
],
2012-03-14 09:32:44 +01:00
depends=['state'], select=True)
2012-03-05 10:53:07 +01:00
reference = fields.Char('Reference', readonly=True, select=True)
2010-01-20 11:03:54 +01:00
description = fields.Char('Description',
2011-08-04 16:57:40 +02:00
states={
2011-08-21 11:20:51 +02:00
'readonly': Eval('state') != 'draft',
2011-08-04 16:57:40 +02:00
},
depends=['state'])
2008-09-03 21:28:12 +02:00
state = fields.Selection([
('draft', 'Draft'),
('quotation', 'Quotation'),
('confirmed', 'Confirmed'),
('processing', 'Processing'),
2008-09-03 21:28:12 +02:00
('done', 'Done'),
2009-03-20 13:07:09 +01:00
('cancel', 'Canceled'),
2008-09-03 21:28:12 +02:00
], 'State', readonly=True, required=True)
2011-12-01 15:20:23 +01:00
sale_date = fields.Date('Sale Date',
2011-08-04 16:57:40 +02:00
states={
2011-12-01 15:20:23 +01:00
'readonly': ~Eval('state').in_(['draft', 'quotation']),
'required': ~Eval('state').in_(['draft', 'quotation', 'cancel']),
2011-08-04 16:57:40 +02:00
},
depends=['state'])
2008-09-03 21:28:12 +02:00
payment_term = fields.Many2One('account.invoice.payment_term',
2011-08-04 16:57:40 +02:00
'Payment Term', required=True, states={
2011-08-21 11:20:51 +02:00
'readonly': Eval('state') != 'draft',
2011-08-04 16:57:40 +02:00
},
depends=['state'])
2012-03-05 10:53:07 +01:00
party = fields.Many2One('party.party', 'Party', required=True, select=True,
2011-12-22 22:06:41 +01:00
states={
2014-07-11 18:18:36 +02:00
'readonly': ((Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('party'))),
2014-01-26 14:29:31 +01:00
},
2011-08-04 16:57:40 +02:00
depends=['state'])
2014-01-26 14:29:31 +01:00
party_lang = fields.Function(fields.Char('Party Language'),
'on_change_with_party_lang')
2008-10-22 20:12:40 +02:00
invoice_address = fields.Many2One('party.address', 'Invoice Address',
2011-08-04 16:57:40 +02:00
domain=[('party', '=', Eval('party'))], states={
2011-08-21 11:20:51 +02:00
'readonly': Eval('state') != 'draft',
'required': ~Eval('state').in_(['draft', 'quotation', 'cancel']),
2011-08-04 16:57:40 +02:00
}, depends=['state', 'party'])
shipment_address = fields.Many2One('party.address', 'Shipment Address',
2011-08-04 16:57:40 +02:00
domain=[('party', '=', Eval('party'))], states={
2011-08-21 11:20:51 +02:00
'readonly': Eval('state') != 'draft',
'required': ~Eval('state').in_(['draft', 'quotation', 'cancel']),
2011-08-04 16:57:40 +02:00
},
depends=['party', 'state'])
2008-09-03 21:28:12 +02:00
warehouse = fields.Many2One('stock.location', 'Warehouse',
2011-11-19 19:37:11 +01:00
domain=[('type', '=', 'warehouse')], states={
2011-08-21 11:20:51 +02:00
'readonly': Eval('state') != 'draft',
2011-08-04 16:57:40 +02:00
},
depends=['state'])
2008-09-03 21:28:12 +02:00
currency = fields.Many2One('currency.currency', 'Currency', required=True,
states={
2014-12-02 12:55:52 +01:00
'readonly': ((Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('currency', 0))),
2011-08-04 16:57:40 +02:00
},
depends=['state'])
2014-01-26 14:29:31 +01:00
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'on_change_with_currency_digits')
2008-09-03 21:28:12 +02:00
lines = fields.One2Many('sale.line', 'sale', 'Lines', states={
2011-08-21 11:20:51 +02:00
'readonly': Eval('state') != 'draft',
2014-01-26 14:29:31 +01:00
},
2011-08-04 16:57:40 +02:00
depends=['party', 'state'])
2008-09-03 21:28:12 +02:00
comment = fields.Text('Comment')
2010-03-11 16:53:27 +01:00
untaxed_amount = fields.Function(fields.Numeric('Untaxed',
2011-08-04 16:57:40 +02:00
digits=(16, Eval('currency_digits', 2)),
2013-06-01 00:01:39 +02:00
depends=['currency_digits']), 'get_amount')
untaxed_amount_cache = fields.Numeric('Untaxed Cache',
digits=(16, Eval('currency_digits', 2)),
readonly=True,
depends=['currency_digits'])
2010-03-11 16:53:27 +01:00
tax_amount = fields.Function(fields.Numeric('Tax',
2011-08-04 16:57:40 +02:00
digits=(16, Eval('currency_digits', 2)),
2013-06-01 00:01:39 +02:00
depends=['currency_digits']), 'get_amount')
tax_amount_cache = fields.Numeric('Tax Cache',
digits=(16, Eval('currency_digits', 2)),
readonly=True,
depends=['currency_digits'])
2010-03-11 16:53:27 +01:00
total_amount = fields.Function(fields.Numeric('Total',
2011-08-04 16:57:40 +02:00
digits=(16, Eval('currency_digits', 2)),
2013-06-01 00:01:39 +02:00
depends=['currency_digits']), 'get_amount')
total_amount_cache = fields.Numeric('Total Tax',
digits=(16, Eval('currency_digits', 2)),
readonly=True,
depends=['currency_digits'])
2008-09-03 21:28:12 +02:00
invoice_method = fields.Selection([
2011-08-04 16:57:40 +02:00
('manual', 'Manual'),
('order', 'On Order Processed'),
2011-08-04 16:57:40 +02:00
('shipment', 'On Shipment Sent'),
],
'Invoice Method', required=True, states={
2011-08-21 11:20:51 +02:00
'readonly': Eval('state') != 'draft',
2011-08-04 16:57:40 +02:00
},
depends=['state'])
2008-09-03 21:28:12 +02:00
invoice_state = fields.Selection([
2012-03-25 00:18:13 +01:00
('none', 'None'),
('waiting', 'Waiting'),
('paid', 'Paid'),
('exception', 'Exception'),
], 'Invoice State', readonly=True, required=True)
invoices = fields.Function(fields.One2Many('account.invoice', None,
'Invoices'), 'get_invoices', searcher='search_invoices')
2009-03-05 14:53:56 +01:00
invoices_ignored = fields.Many2Many('sale.sale-ignored-account.invoice',
'sale', 'invoice', 'Ignored Invoices', readonly=True)
2012-04-03 20:07:35 +02:00
invoices_recreated = fields.Many2Many(
'sale.sale-recreated-account.invoice', 'sale', 'invoice',
'Recreated Invoices', readonly=True)
shipment_method = fields.Selection([
2011-08-04 16:57:40 +02:00
('manual', 'Manual'),
('order', 'On Order Processed'),
2011-08-04 16:57:40 +02:00
('invoice', 'On Invoice Paid'),
], 'Shipment Method', required=True,
states={
2011-08-21 11:20:51 +02:00
'readonly': Eval('state') != 'draft',
2011-08-04 16:57:40 +02:00
},
depends=['state'])
shipment_state = fields.Selection([
2012-03-25 00:18:13 +01:00
('none', 'None'),
('waiting', 'Waiting'),
('sent', 'Sent'),
('exception', 'Exception'),
], 'Shipment State', readonly=True, required=True)
2010-03-11 16:53:27 +01:00
shipments = fields.Function(fields.One2Many('stock.shipment.out', None,
2013-09-19 17:34:09 +02:00
'Shipments'), 'get_shipments', searcher='search_shipments')
2012-04-02 12:48:09 +02:00
shipment_returns = fields.Function(
fields.One2Many('stock.shipment.out.return', None, 'Shipment Returns'),
2013-09-19 17:34:09 +02:00
'get_shipment_returns', searcher='search_shipment_returns')
2010-03-11 16:53:27 +01:00
moves = fields.Function(fields.One2Many('stock.move', None, 'Moves'),
2012-09-24 22:18:13 +02:00
'get_moves')
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@classmethod
def __setup__(cls):
super(Sale, cls).__setup__()
cls._order.insert(0, ('sale_date', 'DESC'))
cls._order.insert(1, ('id', 'DESC'))
cls._error_messages.update({
'invalid_method': ('Invalid combination of shipment and '
'invoicing methods on sale "%s".'),
2013-10-05 12:17:20 +02:00
'addresses_required': (
'Invoice and Shipment addresses must be '
'defined for the quotation of sale "%s".'),
2013-02-24 15:51:12 +01:00
'warehouse_required': ('Warehouse must be defined for the '
'quotation of sale "%s".'),
'missing_account_receivable': ('It misses '
'an "Account Receivable" on the party "%s".'),
'delete_cancel': ('Sale "%s" must be cancelled before '
'deletion.'),
2012-09-24 22:18:13 +02:00
})
cls._transitions |= set((
2012-03-25 00:18:13 +01:00
('draft', 'quotation'),
('quotation', 'confirmed'),
('confirmed', 'processing'),
('processing', 'processing'),
('processing', 'done'),
2014-02-03 17:03:57 +01:00
('done', 'processing'),
2012-03-25 00:18:13 +01:00
('draft', 'cancel'),
('quotation', 'cancel'),
('quotation', 'draft'),
2012-05-12 16:41:50 +02:00
('cancel', 'draft'),
2012-03-25 00:18:13 +01:00
))
2012-09-24 22:18:13 +02:00
cls._buttons.update({
2012-03-25 00:18:13 +01:00
'cancel': {
'invisible': ~Eval('state').in_(['draft', 'quotation']),
2012-03-25 00:18:13 +01:00
},
'draft': {
2012-05-12 16:41:50 +02:00
'invisible': ~Eval('state').in_(['cancel', 'quotation']),
'icon': If(Eval('state') == 'cancel', 'tryton-clear',
'tryton-go-previous'),
2012-03-25 00:18:13 +01:00
},
'quote': {
'invisible': Eval('state') != 'draft',
'readonly': ~Eval('lines', []),
},
'confirm': {
'invisible': Eval('state') != 'quotation',
},
'process': {
'invisible': Eval('state') != 'confirmed',
},
2012-09-10 15:28:33 +02:00
'handle_invoice_exception': {
'invisible': ((Eval('invoice_state') != 'exception')
| (Eval('state') == 'cancel')),
'readonly': ~Eval('groups', []).contains(
Id('sale', 'group_sale')),
},
'handle_shipment_exception': {
'invisible': ((Eval('shipment_state') != 'exception')
| (Eval('state') == 'cancel')),
'readonly': ~Eval('groups', []).contains(
Id('sale', 'group_sale')),
},
2012-03-25 00:18:13 +01:00
})
# The states where amounts are cached
2012-09-24 22:18:13 +02:00
cls._states_cached = ['confirmed', 'processing', 'done', 'cancel']
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@classmethod
def __register__(cls, module_name):
2012-05-19 15:34:10 +02:00
pool = Pool()
2012-09-24 22:18:13 +02:00
SaleLine = pool.get('sale.line')
2013-09-06 22:34:40 +02:00
TableHandler = backend.get('TableHandler')
sale_line_invoice_line_table_name = 'sale_line_invoice_lines_rel'
2012-09-24 22:18:13 +02:00
Move = pool.get('stock.move')
2010-08-10 11:58:48 -04:00
cursor = Transaction().cursor
2013-09-06 22:34:40 +02:00
model_data = Table('ir_model_data')
model_field = Table('ir_model_field')
sql_table = cls.__table__()
# Migration from 1.2: packing renamed into shipment
2013-09-06 22:34:40 +02:00
cursor.execute(*model_data.update(
columns=[model_data.fs_id],
values=[Overlay(model_data.fs_id, 'shipment',
Position('packing', model_data.fs_id),
len('packing'))],
where=model_data.fs_id.like('%packing%')
& (model_data.module == module_name)))
cursor.execute(*model_field.update(
columns=[model_field.relation],
values=[Overlay(model_field.relation, 'shipment',
Position('packing', model_field.relation),
len('packing'))],
where=model_field.relation.like('%packing%')
& (model_field.module == module_name)))
cursor.execute(*model_field.update(
columns=[model_field.name],
values=[Overlay(model_field.name, 'shipment',
Position('packing', model_field.name),
len('packing'))],
where=model_field.name.like('%packing%')
& (model_field.module == module_name)))
2012-09-24 22:18:13 +02:00
table = TableHandler(cursor, cls, module_name)
table.column_rename('packing_state', 'shipment_state')
table.column_rename('packing_method', 'shipment_method')
table.column_rename('packing_address', 'shipment_address')
2012-09-24 22:18:13 +02:00
super(Sale, cls).__register__(module_name)
# Migration from 1.2
2013-09-06 22:34:40 +02:00
cursor.execute(*sql_table.update(
columns=[sql_table.invoice_method],
values=['shipment'],
where=sql_table.invoice_method == 'packing'))
2012-09-24 22:18:13 +02:00
table = TableHandler(cursor, cls, module_name)
2011-12-01 15:20:23 +01:00
# Migration from 2.2
table.not_null_action('sale_date', 'remove')
# state confirmed splitted into confirmed and processing
2012-09-24 22:18:13 +02:00
if (TableHandler.table_exist(cursor, SaleLine._table)
2012-05-19 15:34:10 +02:00
and TableHandler.table_exist(cursor,
2013-09-06 22:34:40 +02:00
sale_line_invoice_line_table_name)
2012-09-24 22:18:13 +02:00
and TableHandler.table_exist(cursor, Move._table)):
2013-09-06 22:34:40 +02:00
sale_line = SaleLine.__table__()
sale_line_invoice_line = \
Table(sale_line_invoice_line_table_name)
move = Move.__table__()
# Wrap subquery inside an other inner subquery because MySQL syntax
# doesn't allow update a table and select from the same table in a
# subquery.
2013-09-06 22:34:40 +02:00
sub_query = sql_table.join(sale_line,
condition=sale_line.sale == sql_table.id
).join(sale_line_invoice_line, 'LEFT',
condition=sale_line_invoice_line.sale_line == sale_line.id
).join(move, 'LEFT',
condition=(move.origin == Concat(SaleLine.__name__,
sale_line.id))
).select(sql_table.id,
where=(sql_table.state == 'confirmed')
& ((sale_line_invoice_line.id != Null)
| (move.id != Null)))
2013-09-06 22:34:40 +02:00
cursor.execute(*sql_table.update(
columns=[sql_table.state],
values=['processing'],
where=sql_table.id.in_(sub_query.select(sub_query.id))))
2010-01-23 11:42:47 +01:00
# Add index on create_date
2012-09-24 22:18:13 +02:00
table = TableHandler(cursor, cls, module_name)
2010-01-23 11:42:47 +01:00
table.index_action('create_date', action='add')
2012-09-24 22:18:13 +02:00
@classmethod
def default_payment_term(cls):
PaymentTerm = Pool().get('account.invoice.payment_term')
payment_terms = PaymentTerm.search(cls.payment_term.domain)
if len(payment_terms) == 1:
return payment_terms[0].id
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@classmethod
def default_warehouse(cls):
Location = Pool().get('stock.location')
locations = Location.search(cls.warehouse.domain)
if len(locations) == 1:
return locations[0].id
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@staticmethod
def default_company():
2012-03-27 10:43:58 +02:00
return Transaction().context.get('company')
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@staticmethod
def default_state():
2008-09-03 21:28:12 +02:00
return 'draft'
2012-09-24 22:18:13 +02:00
@staticmethod
def default_currency():
Company = Pool().get('company.company')
2010-08-10 11:58:48 -04:00
company = Transaction().context.get('company')
if company:
2012-09-24 22:18:13 +02:00
return Company(company).currency.id
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@staticmethod
def default_currency_digits():
Company = Pool().get('company.company')
2010-08-10 11:58:48 -04:00
company = Transaction().context.get('company')
if company:
2012-09-24 22:18:13 +02:00
return Company(company).currency.digits
2008-09-30 17:13:07 +02:00
return 2
2012-09-24 22:18:13 +02:00
@staticmethod
def default_invoice_method():
Config = Pool().get('sale.configuration')
config = Config(1)
2011-03-29 15:18:40 +02:00
return config.sale_invoice_method
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@staticmethod
def default_invoice_state():
2008-09-03 21:28:12 +02:00
return 'none'
2012-09-24 22:18:13 +02:00
@staticmethod
def default_shipment_method():
Config = Pool().get('sale.configuration')
config = Config(1)
2011-03-29 15:18:40 +02:00
return config.sale_shipment_method
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@staticmethod
def default_shipment_state():
2008-09-03 21:28:12 +02:00
return 'none'
2014-01-26 14:29:31 +01:00
@fields.depends('party', 'payment_term')
2012-09-24 22:18:13 +02:00
def on_change_party(self):
2014-10-29 20:40:13 +01:00
self.invoice_address = None
self.shipment_address = None
self.payment_term = self.default_payment_term()
2012-09-24 22:18:13 +02:00
if self.party:
2014-10-29 20:40:13 +01:00
self.invoice_address = self.party.address_get(type='invoice')
self.shipment_address = self.party.address_get(type='delivery')
self.payment_term = self.party.customer_payment_term
2008-09-03 21:28:12 +02:00
2014-01-26 14:29:31 +01:00
@fields.depends('currency')
2012-09-24 22:18:13 +02:00
def on_change_with_currency_digits(self, name=None):
if self.currency:
return self.currency.digits
2008-09-03 21:28:12 +02:00
return 2
2012-09-24 22:18:13 +02:00
def get_tax_context(self):
2008-09-03 21:28:12 +02:00
res = {}
2012-09-24 22:18:13 +02:00
if self.party and self.party.lang:
res['language'] = self.party.lang.code
2008-09-03 21:28:12 +02:00
return res
2014-01-26 14:29:31 +01:00
@fields.depends('party')
2012-09-24 22:18:13 +02:00
def on_change_with_party_lang(self, name=None):
2012-11-23 18:01:04 +01:00
Config = Pool().get('ir.configuration')
2012-09-24 22:18:13 +02:00
if self.party and self.party.lang:
return self.party.lang.code
2012-11-23 18:01:04 +01:00
return Config.get_language()
2008-09-03 21:28:12 +02:00
2014-01-26 14:29:31 +01:00
@fields.depends('lines', 'currency', 'party')
2012-09-24 22:18:13 +02:00
def on_change_lines(self):
2011-07-03 13:19:11 +02:00
pool = Pool()
2012-09-24 22:18:13 +02:00
Tax = pool.get('account.tax')
Invoice = pool.get('account.invoice')
2014-09-17 22:20:32 +02:00
Configuration = pool.get('account.configuration')
2008-09-03 21:28:12 +02:00
2014-09-17 22:20:32 +02:00
config = Configuration(1)
2014-10-29 20:40:13 +01:00
self.untaxed_amount = Decimal('0.0')
self.tax_amount = Decimal('0.0')
self.total_amount = Decimal('0.0')
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
if self.lines:
2014-09-17 22:20:32 +02:00
context = self.get_tax_context()
taxes = {}
2014-09-17 22:20:32 +02:00
def round_taxes():
if self.currency:
for key, value in taxes.iteritems():
taxes[key] = self.currency.round(value)
2012-09-24 22:18:13 +02:00
for line in self.lines:
if getattr(line, 'type', 'line') != 'line':
2008-09-03 21:28:12 +02:00
continue
2014-10-29 20:40:13 +01:00
self.untaxed_amount += (getattr(line, 'amount', None)
or Decimal(0))
2014-09-17 22:20:32 +02:00
with Transaction().set_context(context):
2012-09-24 22:18:13 +02:00
tax_list = Tax.compute(getattr(line, 'taxes', []),
getattr(line, 'unit_price', None) or Decimal('0.0'),
getattr(line, 'quantity', None) or 0.0)
2010-08-10 11:58:48 -04:00
for tax in tax_list:
2012-09-24 22:18:13 +02:00
key, val = Invoice._compute_tax(tax, 'out_invoice')
2014-09-17 22:20:32 +02:00
if key not in taxes:
taxes[key] = val['amount']
else:
taxes[key] += val['amount']
2014-09-17 22:20:32 +02:00
if config.tax_rounding == 'line':
round_taxes()
if config.tax_rounding == 'document':
round_taxes()
2014-10-29 20:40:13 +01:00
self.tax_amount = sum(taxes.itervalues(), Decimal('0.0'))
2012-09-24 22:18:13 +02:00
if self.currency:
2014-10-29 20:40:13 +01:00
self.untaxed_amount = self.currency.round(self.untaxed_amount)
self.tax_amount = self.currency.round(self.tax_amount)
self.total_amount = self.untaxed_amount + self.tax_amount
2012-09-24 22:18:13 +02:00
if self.currency:
2014-10-29 20:40:13 +01:00
self.total_amount = self.currency.round(self.total_amount)
2008-09-03 21:28:12 +02:00
2013-06-01 00:01:39 +02:00
def get_tax_amount(self):
2011-07-03 13:19:11 +02:00
pool = Pool()
2012-09-24 22:18:13 +02:00
Tax = pool.get('account.tax')
Invoice = pool.get('account.invoice')
2014-09-17 22:20:32 +02:00
Configuration = pool.get('account.configuration')
config = Configuration(1)
2012-09-24 22:18:13 +02:00
context = self.get_tax_context()
taxes = {}
2014-09-17 22:20:32 +02:00
def round_taxes():
for key, value in taxes.iteritems():
taxes[key] = self.currency.round(value)
2012-09-24 22:18:13 +02:00
for line in self.lines:
if line.type != 'line':
continue
2012-09-24 22:18:13 +02:00
with Transaction().set_context(context):
tax_list = Tax.compute(line.taxes, line.unit_price,
line.quantity)
for tax in tax_list:
key, val = Invoice._compute_tax(tax, 'out_invoice')
2014-09-17 22:20:32 +02:00
if key not in taxes:
2012-09-24 22:18:13 +02:00
taxes[key] = val['amount']
else:
taxes[key] += val['amount']
2014-09-17 22:20:32 +02:00
if config.tax_rounding == 'line':
round_taxes()
if config.tax_rounding == 'document':
round_taxes()
return sum(taxes.itervalues(), _ZERO)
2008-09-03 21:28:12 +02:00
2013-06-01 00:01:39 +02:00
@classmethod
def get_amount(cls, sales, names):
untaxed_amount = {}
tax_amount = {}
total_amount = {}
if {'tax_amount', 'total_amount'} & set(names):
compute_taxes = True
else:
compute_taxes = False
# Sort cached first and re-instanciate to optimize cache management
sales = sorted(sales, key=lambda s: s.state in cls._states_cached,
reverse=True)
sales = cls.browse(sales)
2013-06-01 00:01:39 +02:00
for sale in sales:
if (sale.state in cls._states_cached
and sale.untaxed_amount_cache is not None
and sale.tax_amount_cache is not None
and sale.total_amount_cache is not None):
untaxed_amount[sale.id] = sale.untaxed_amount_cache
if compute_taxes:
tax_amount[sale.id] = sale.tax_amount_cache
total_amount[sale.id] = sale.total_amount_cache
2013-06-01 00:01:39 +02:00
else:
untaxed_amount[sale.id] = sum(
(line.amount for line in sale.lines
if line.type == 'line'), _ZERO)
if compute_taxes:
tax_amount[sale.id] = sale.get_tax_amount()
total_amount[sale.id] = (
untaxed_amount[sale.id] + tax_amount[sale.id])
2013-06-01 00:01:39 +02:00
result = {
'untaxed_amount': untaxed_amount,
'tax_amount': tax_amount,
'total_amount': total_amount,
}
for key in result.keys():
if key not in names:
del result[key]
return result
2008-09-03 21:28:12 +02:00
def get_invoices(self, name):
invoices = set()
for line in self.lines:
for invoice_line in line.invoice_lines:
if invoice_line.invoice:
invoices.add(invoice_line.invoice.id)
return list(invoices)
@classmethod
def search_invoices(cls, name, clause):
return [('lines.invoice_lines.invoice.id',) + tuple(clause[1:])]
2012-09-24 22:18:13 +02:00
def get_invoice_state(self):
'''
2012-03-25 00:18:13 +01:00
Return the invoice state for the sale.
'''
2012-09-24 22:18:13 +02:00
skip_ids = set(x.id for x in self.invoices_ignored)
skip_ids.update(x.id for x in self.invoices_recreated)
invoices = [i for i in self.invoices if i.id not in skip_ids]
2012-03-25 00:18:13 +01:00
if invoices:
if any(i.state == 'cancel' for i in invoices):
return 'exception'
elif all(i.state == 'paid' for i in invoices):
return 'paid'
else:
return 'waiting'
return 'none'
2012-09-24 22:18:13 +02:00
def set_invoice_state(self):
'''
2012-03-25 00:18:13 +01:00
Set the invoice state.
'''
2012-09-24 22:18:13 +02:00
state = self.get_invoice_state()
if self.invoice_state != state:
self.write([self], {
2012-03-25 00:18:13 +01:00
'invoice_state': state,
})
2008-09-03 21:28:12 +02:00
def get_shipments_returns(model_name):
2012-04-02 12:48:09 +02:00
"Computes the returns or shipments"
2012-09-24 22:18:13 +02:00
def method(self, name):
Model = Pool().get(model_name)
shipments = set()
2012-09-24 22:18:13 +02:00
for line in self.lines:
for move in line.moves:
if isinstance(move.shipment, Model):
shipments.add(move.shipment.id)
return list(shipments)
2012-04-02 12:48:09 +02:00
return method
get_shipments = get_shipments_returns('stock.shipment.out')
get_shipment_returns = get_shipments_returns('stock.shipment.out.return')
2008-09-03 21:28:12 +02:00
2013-09-19 17:34:09 +02:00
def search_shipments_returns(model_name):
'''
Search on shipments or returns
'''
def method(self, name, clause):
return [('lines.moves.shipment.id',) + tuple(clause[1:])
+ (model_name,)]
return classmethod(method)
search_shipments = search_shipments_returns('stock.shipment.out')
search_shipment_returns = search_shipments_returns(
'stock.shipment.out.return')
2012-09-24 22:18:13 +02:00
def get_moves(self, name):
return [m.id for l in self.lines for m in l.moves]
2012-09-24 22:18:13 +02:00
def get_shipment_state(self):
'''
2012-03-25 00:18:13 +01:00
Return the shipment state for the sale.
'''
2012-09-24 22:18:13 +02:00
if self.moves:
if any(l.move_exception for l in self.lines):
2012-03-25 00:18:13 +01:00
return 'exception'
2012-09-24 22:18:13 +02:00
elif all(l.move_done for l in self.lines):
2012-03-25 00:18:13 +01:00
return 'sent'
else:
return 'waiting'
return 'none'
2012-09-24 22:18:13 +02:00
def set_shipment_state(self):
'''
2012-03-25 00:18:13 +01:00
Set the shipment state.
'''
2012-09-24 22:18:13 +02:00
state = self.get_shipment_state()
if self.shipment_state != state:
self.write([self], {
2012-03-25 00:18:13 +01:00
'shipment_state': state,
})
2008-09-03 21:28:12 +02:00
@classmethod
def validate(cls, sales):
super(Sale, cls).validate(sales)
for sale in sales:
sale.check_method()
2012-09-24 22:18:13 +02:00
def check_method(self):
2008-09-03 21:28:12 +02:00
'''
Check the methods.
'''
2012-09-24 22:18:13 +02:00
if (self.invoice_method == 'shipment'
and self.shipment_method in ('invoice', 'manual')):
2013-03-25 21:01:32 +00:00
self.raise_user_error('invalid_method', (self.rec_name,))
2012-09-24 22:18:13 +02:00
if (self.shipment_method == 'invoice'
and self.invoice_method in ('shipment', 'manual')):
2013-03-25 21:01:32 +00:00
self.raise_user_error('invalid_method', (self.rec_name,))
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
def get_rec_name(self, name):
return (self.reference or str(self.id)
+ ' - ' + self.party.rec_name)
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@classmethod
def search_rec_name(cls, name, clause):
names = clause[2].split(' - ', 1)
res = [('reference', clause[1], names[0])]
if len(names) != 1 and names[1]:
res.append(('party', clause[1], names[1]))
return res
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@classmethod
def copy(cls, sales, default=None):
2008-09-03 21:28:12 +02:00
if default is None:
default = {}
default = default.copy()
default['state'] = 'draft'
2012-03-27 10:43:58 +02:00
default['reference'] = None
2008-09-03 21:28:12 +02:00
default['invoice_state'] = 'none'
2012-03-27 10:43:58 +02:00
default['invoices_ignored'] = None
default['shipment_state'] = 'none'
2012-03-27 10:43:58 +02:00
default.setdefault('sale_date', None)
2012-09-24 22:18:13 +02:00
return super(Sale, cls).copy(sales, default=default)
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
def check_for_quotation(self):
if not self.invoice_address or not self.shipment_address:
2013-03-25 21:01:32 +00:00
self.raise_user_error('addresses_required', (self.rec_name,))
2012-09-24 22:18:13 +02:00
for line in self.lines:
if line.quantity >= 0:
location = line.from_location
else:
location = line.to_location
if ((not location or not line.warehouse)
and line.product
and line.product.type in ('goods', 'assets')):
self.raise_user_error('warehouse_required',
2013-03-25 21:01:32 +00:00
(self.rec_name,))
2012-09-24 22:18:13 +02:00
@classmethod
def set_reference(cls, sales):
2008-09-03 21:28:12 +02:00
'''
Fill the reference field with the sale sequence
'''
2012-09-24 22:18:13 +02:00
pool = Pool()
Sequence = pool.get('ir.sequence')
Config = pool.get('sale.configuration')
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
config = Config(1)
2012-03-25 00:18:13 +01:00
for sale in sales:
if sale.reference:
continue
2012-09-24 22:18:13 +02:00
reference = Sequence.get_id(config.sale_sequence.id)
cls.write([sale], {
'reference': reference,
})
2012-03-25 00:18:13 +01:00
2012-09-24 22:18:13 +02:00
@classmethod
def set_sale_date(cls, sales):
Date = Pool().get('ir.date')
for sale in sales:
2012-03-25 00:18:13 +01:00
if not sale.sale_date:
2012-09-24 22:18:13 +02:00
cls.write([sale], {
'sale_date': Date.today(),
2012-03-25 00:18:13 +01:00
})
2012-09-24 22:18:13 +02:00
@classmethod
def store_cache(cls, sales):
for sale in sales:
cls.write([sale], {
2012-03-25 00:18:13 +01:00
'untaxed_amount_cache': sale.untaxed_amount,
'tax_amount_cache': sale.tax_amount,
'total_amount_cache': sale.total_amount,
2011-12-01 15:20:23 +01:00
})
2011-09-29 12:38:21 +02:00
2012-09-24 22:18:13 +02:00
def _get_invoice_line_sale_line(self, invoice_type):
2008-09-03 21:28:12 +02:00
'''
2012-09-24 22:18:13 +02:00
Return invoice line for each sale lines according to invoice_type
2008-09-03 21:28:12 +02:00
'''
res = {}
2012-09-24 22:18:13 +02:00
for line in self.lines:
val = line.get_invoice_line(invoice_type)
2008-09-03 21:28:12 +02:00
if val:
res[line.id] = val
return res
2012-09-24 22:18:13 +02:00
def _get_invoice_sale(self, invoice_type):
'''
2012-09-24 22:18:13 +02:00
Return invoice of type invoice_type
2008-09-03 21:28:12 +02:00
'''
2011-07-03 13:19:11 +02:00
pool = Pool()
2012-09-24 22:18:13 +02:00
Invoice = pool.get('account.invoice')
Journal = pool.get('account.journal')
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
journals = Journal.search([
('type', '=', 'revenue'),
], limit=1)
if journals:
journal, = journals
else:
journal = None
2014-09-17 21:45:49 +02:00
return Invoice(
company=self.company,
type=invoice_type,
journal=journal,
party=self.party,
invoice_address=self.invoice_address,
currency=self.currency,
account=self.party.account_receivable,
payment_term=self.payment_term,
)
2012-09-24 22:18:13 +02:00
def create_invoice(self, invoice_type):
'''
Create and return an invoice of type invoice_type
'''
pool = Pool()
Invoice = pool.get('account.invoice')
if self.invoice_method == 'manual':
return
2012-09-24 22:18:13 +02:00
if not self.party.account_receivable:
2010-08-10 11:58:48 -04:00
self.raise_user_error('missing_account_receivable',
2013-03-25 21:01:32 +00:00
(self.party.rec_name,))
2012-09-24 22:18:13 +02:00
invoice_lines = self._get_invoice_line_sale_line(invoice_type)
2008-09-03 21:28:12 +02:00
if not invoice_lines:
return
2012-09-24 22:18:13 +02:00
invoice = self._get_invoice_sale(invoice_type)
invoice.lines = ((list(invoice.lines)
if hasattr(invoice, 'lines') else [])
+ list(chain.from_iterable(invoice_lines.itervalues())))
2012-09-24 22:18:13 +02:00
invoice.save()
2008-09-03 21:28:12 +02:00
2014-09-17 21:45:49 +02:00
Invoice.update_taxes([invoice])
2012-09-24 22:18:13 +02:00
return invoice
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
def _get_move_sale_line(self, shipment_type):
2008-09-03 21:28:12 +02:00
'''
2012-09-24 22:18:13 +02:00
Return move for each sale lines of the right shipment_type
2008-09-03 21:28:12 +02:00
'''
res = {}
2012-09-24 22:18:13 +02:00
for line in self.lines:
val = line.get_move(shipment_type)
2012-04-21 01:19:39 +02:00
if val:
2008-09-03 21:28:12 +02:00
res[line.id] = val
return res
2011-09-29 12:38:21 +02:00
def _group_shipment_key(self, moves, move):
'''
The key to group moves by shipments
2012-09-24 22:18:13 +02:00
move is a tuple of line id and a move
2011-09-29 12:38:21 +02:00
'''
2012-09-24 22:18:13 +02:00
SaleLine = Pool().get('sale.line')
2011-11-19 19:37:11 +01:00
line_id, move = move
2012-09-24 22:18:13 +02:00
line = SaleLine(line_id)
2011-11-19 19:37:11 +01:00
2012-09-24 22:18:13 +02:00
planned_date = max(m.planned_date for m in moves)
2011-11-19 19:37:11 +01:00
return (
('planned_date', planned_date),
('warehouse', line.warehouse.id),
)
2011-09-29 12:38:21 +02:00
2012-04-02 12:48:09 +02:00
_group_return_key = _group_shipment_key
2014-03-16 21:40:29 +01:00
def _get_shipment_sale(self, Shipment, key):
values = {
'customer': self.party.id,
'delivery_address': self.shipment_address.id,
'company': self.company.id,
}
values.update(dict(key))
return Shipment(**values)
2012-09-24 22:18:13 +02:00
def create_shipment(self, shipment_type):
2008-09-03 21:28:12 +02:00
'''
2012-09-24 22:18:13 +02:00
Create and return shipments of type shipment_type
2008-09-03 21:28:12 +02:00
'''
2011-07-03 13:19:11 +02:00
pool = Pool()
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
if self.shipment_method == 'manual':
return
2012-09-24 22:18:13 +02:00
moves = self._get_move_sale_line(shipment_type)
2008-09-03 21:28:12 +02:00
if not moves:
return
2012-04-02 12:48:09 +02:00
if shipment_type == 'out':
keyfunc = partial(self._group_shipment_key, moves.values())
2012-09-24 22:18:13 +02:00
Shipment = pool.get('stock.shipment.out')
2012-04-02 12:48:09 +02:00
elif shipment_type == 'return':
keyfunc = partial(self._group_return_key, moves.values())
2012-09-24 22:18:13 +02:00
Shipment = pool.get('stock.shipment.out.return')
2011-09-29 12:38:21 +02:00
moves = moves.items()
moves = sorted(moves, key=keyfunc)
2008-09-03 21:28:12 +02:00
2011-09-29 12:38:21 +02:00
shipments = []
2012-09-24 22:18:13 +02:00
for key, grouped_moves in groupby(moves, key=keyfunc):
2014-09-17 21:45:49 +02:00
shipment = self._get_shipment_sale(Shipment, key)
shipment.moves = (list(getattr(shipment, 'moves', []))
+ [x[1] for x in grouped_moves])
shipment.save()
shipments.append(shipment)
2012-09-24 22:18:13 +02:00
if shipment_type == 'out':
2014-09-17 21:45:49 +02:00
Shipment.wait(shipments)
2011-09-29 12:38:21 +02:00
return shipments
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
def is_done(self):
return ((self.invoice_state == 'paid'
or self.invoice_state == 'none')
2012-09-24 22:18:13 +02:00
and (self.shipment_state == 'sent'
or self.shipment_state == 'none'
or all(l.product.type == 'service'
for l in self.lines if l.product)))
2012-09-24 22:18:13 +02:00
@classmethod
def delete(cls, sales):
# Cancel before delete
2012-09-24 22:18:13 +02:00
cls.cancel(sales)
for sale in sales:
if sale.state != 'cancel':
2013-03-25 21:01:32 +00:00
cls.raise_user_error('delete_cancel', (sale.rec_name,))
2012-09-24 22:18:13 +02:00
super(Sale, cls).delete(sales)
2012-09-24 22:18:13 +02:00
@classmethod
2012-03-25 00:18:13 +01:00
@ModelView.button
@Workflow.transition('cancel')
2012-09-24 22:18:13 +02:00
def cancel(cls, sales):
cls.store_cache(sales)
2012-09-24 22:18:13 +02:00
@classmethod
2012-03-25 00:18:13 +01:00
@ModelView.button
@Workflow.transition('draft')
2012-09-24 22:18:13 +02:00
def draft(cls, sales):
2012-03-25 00:18:13 +01:00
pass
2012-09-24 22:18:13 +02:00
@classmethod
2012-03-25 00:18:13 +01:00
@ModelView.button
@Workflow.transition('quotation')
2012-09-24 22:18:13 +02:00
def quote(cls, sales):
for sale in sales:
sale.check_for_quotation()
cls.set_reference(sales)
2012-09-24 22:18:13 +02:00
@classmethod
2012-03-25 00:18:13 +01:00
@ModelView.button
@Workflow.transition('confirmed')
2012-09-24 22:18:13 +02:00
def confirm(cls, sales):
cls.set_sale_date(sales)
cls.store_cache(sales)
2012-09-24 22:18:13 +02:00
@classmethod
2012-09-10 15:28:33 +02:00
@ModelView.button_action('sale.wizard_invoice_handle_exception')
2012-09-24 22:18:13 +02:00
def handle_invoice_exception(cls, sales):
2012-09-10 15:28:33 +02:00
pass
2012-09-24 22:18:13 +02:00
@classmethod
2012-09-10 15:28:33 +02:00
@ModelView.button_action('sale.wizard_shipment_handle_exception')
2012-09-24 22:18:13 +02:00
def handle_shipment_exception(cls, sales):
2012-09-10 15:28:33 +02:00
pass
2012-09-24 22:18:13 +02:00
@classmethod
2012-03-25 00:18:13 +01:00
@Workflow.transition('processing')
def proceed(cls, sales):
pass
@classmethod
@Workflow.transition('done')
def do(cls, sales):
pass
@classmethod
@ModelView.button
2012-09-24 22:18:13 +02:00
def process(cls, sales):
2012-03-25 00:18:13 +01:00
done = []
process = []
2012-09-24 22:18:13 +02:00
for sale in sales:
if sale.state not in ('confirmed', 'processing', 'done'):
continue
2012-09-24 22:18:13 +02:00
sale.create_invoice('out_invoice')
sale.create_invoice('out_credit_note')
sale.set_invoice_state()
sale.create_shipment('out')
sale.create_shipment('return')
sale.set_shipment_state()
if sale.is_done():
if sale.state != 'done':
done.append(sale)
elif sale.state != 'processing':
process.append(sale)
if process:
cls.proceed(process)
2012-03-25 00:18:13 +01:00
if done:
cls.do(done)
2008-09-03 21:28:12 +02:00
2009-03-05 14:53:56 +01:00
class SaleIgnoredInvoice(ModelSQL):
'Sale - Ignored Invoice'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.sale-ignored-account.invoice'
2009-03-05 14:53:56 +01:00
_table = 'sale_invoice_ignored_rel'
2012-04-03 20:07:35 +02:00
sale = fields.Many2One('sale.sale', 'Sale', ondelete='CASCADE',
select=True, required=True)
2009-03-05 14:53:56 +01:00
invoice = fields.Many2One('account.invoice', 'Invoice',
2012-03-05 10:53:07 +01:00
ondelete='RESTRICT', select=True, required=True)
2009-03-05 14:53:56 +01:00
class SaleRecreatedInvoice(ModelSQL):
'Sale - Recreated Invoice'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.sale-recreated-account.invoice'
2009-03-05 14:53:56 +01:00
_table = 'sale_invoice_recreated_rel'
2012-04-03 20:07:35 +02:00
sale = fields.Many2One('sale.sale', 'Sale', ondelete='CASCADE',
select=True, required=True)
2009-03-05 14:53:56 +01:00
invoice = fields.Many2One('account.invoice', 'Invoice',
2012-03-05 10:53:07 +01:00
ondelete='RESTRICT', select=True, required=True)
2009-03-05 14:53:56 +01:00
2009-02-28 17:04:17 +01:00
class SaleLine(ModelSQL, ModelView):
2008-09-03 21:28:12 +02:00
'Sale Line'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.line'
2008-09-03 21:28:12 +02:00
_rec_name = 'description'
sale = fields.Many2One('sale.sale', 'Sale', ondelete='CASCADE',
select=True)
2013-09-06 22:34:40 +02:00
sequence = fields.Integer('Sequence')
2008-09-03 21:28:12 +02:00
type = fields.Selection([
('line', 'Line'),
('subtotal', 'Subtotal'),
('title', 'Title'),
2009-01-12 14:30:00 +01:00
('comment', 'Comment'),
2012-03-05 10:53:07 +01:00
], 'Type', select=True, required=True)
2008-09-03 21:28:12 +02:00
quantity = fields.Float('Quantity',
2011-08-04 16:57:40 +02:00
digits=(16, Eval('unit_digits', 2)),
states={
2011-08-21 11:20:51 +02:00
'invisible': Eval('type') != 'line',
'required': Eval('type') == 'line',
'readonly': ~Eval('_parent_sale', {}),
2014-01-26 14:29:31 +01:00
},
2011-08-04 16:57:40 +02:00
depends=['type', 'unit_digits'])
2008-09-03 21:28:12 +02:00
unit = fields.Many2One('product.uom', 'Unit',
states={
2010-01-20 11:03:54 +01:00
'required': Bool(Eval('product')),
2011-08-21 11:20:51 +02:00
'invisible': Eval('type') != 'line',
'readonly': ~Eval('_parent_sale', {}),
2010-01-20 11:03:54 +01:00
},
2012-03-25 15:40:02 +02:00
domain=[
If(Bool(Eval('product_uom_category')),
('category', '=', Eval('product_uom_category')),
('category', '!=', -1)),
],
depends=['product', 'type', 'product_uom_category'])
2014-01-26 14:29:31 +01:00
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'on_change_with_unit_digits')
2008-09-03 21:28:12 +02:00
product = fields.Many2One('product.product', 'Product',
2011-08-21 11:20:51 +02:00
domain=[('salable', '=', True)],
states={
'invisible': Eval('type') != 'line',
'readonly': ~Eval('_parent_sale', {}),
},
context={
2012-04-03 20:07:35 +02:00
'locations': If(Bool(Eval('_parent_sale', {}).get('warehouse')),
2011-08-21 11:20:51 +02:00
[Eval('_parent_sale', {}).get('warehouse', 0)], []),
'stock_date_end': Eval('_parent_sale', {}).get('sale_date'),
'stock_skip_warehouse': True,
2011-08-04 16:57:40 +02:00
}, depends=['type'])
2012-03-25 15:40:02 +02:00
product_uom_category = fields.Function(
2014-01-26 14:29:31 +01:00
fields.Many2One('product.uom.category', 'Product Uom Category'),
2012-09-24 22:18:13 +02:00
'on_change_with_product_uom_category')
2008-09-03 21:28:12 +02:00
unit_price = fields.Numeric('Unit Price', digits=(16, 4),
2011-08-21 11:20:51 +02:00
states={
'invisible': Eval('type') != 'line',
'required': Eval('type') == 'line',
2011-08-04 16:57:40 +02:00
}, depends=['type'])
2010-03-11 16:53:27 +01:00
amount = fields.Function(fields.Numeric('Amount',
2011-08-21 11:20:51 +02:00
digits=(16, Eval('_parent_sale', {}).get('currency_digits', 2)),
states={
'invisible': ~Eval('type').in_(['line', 'subtotal']),
'readonly': ~Eval('_parent_sale'),
2014-01-26 14:29:31 +01:00
},
2011-08-21 11:20:51 +02:00
depends=['type']), 'get_amount')
2009-01-10 16:47:21 +01:00
description = fields.Text('Description', size=None, required=True)
note = fields.Text('Note')
2009-03-05 14:53:56 +01:00
taxes = fields.Many2Many('sale.line-account.tax', 'line', 'tax', 'Taxes',
domain=[('parent', '=', None), ['OR',
('group', '=', None),
('group.kind', 'in', ['sale', 'both'])],
2014-12-03 16:14:05 +01:00
('company', '=',
Eval('_parent_sale', {}).get('company', -1)),
],
states={
2011-08-21 11:20:51 +02:00
'invisible': Eval('type') != 'line',
2011-08-04 16:57:40 +02:00
}, depends=['type'])
invoice_lines = fields.One2Many('account.invoice.line', 'origin',
'Invoice Lines', readonly=True)
moves = fields.One2Many('stock.move', 'origin', 'Moves', readonly=True)
2009-03-05 14:53:56 +01:00
moves_ignored = fields.Many2Many('sale.line-ignored-stock.move',
2009-02-24 16:39:55 +01:00
'sale_line', 'move', 'Ignored Moves', readonly=True)
2009-03-05 14:53:56 +01:00
moves_recreated = fields.Many2Many('sale.line-recreated-stock.move',
'sale_line', 'move', 'Recreated Moves', readonly=True)
2010-03-11 16:53:27 +01:00
move_done = fields.Function(fields.Boolean('Moves Done'), 'get_move_done')
move_exception = fields.Function(fields.Boolean('Moves Exception'),
'get_move_exception')
2011-11-19 19:37:11 +01:00
warehouse = fields.Function(fields.Many2One('stock.location',
'Warehouse'), 'get_warehouse')
from_location = fields.Function(fields.Many2One('stock.location',
'From Location'), 'get_from_location')
to_location = fields.Function(fields.Many2One('stock.location',
'To Location'), 'get_to_location')
delivery_date = fields.Function(fields.Date('Delivery Date',
2012-03-25 15:51:48 +02:00
states={
'invisible': ((Eval('type') != 'line')
| (If(Bool(Eval('quantity')), Eval('quantity', 0), 0)
<= 0)),
2012-03-25 15:51:48 +02:00
},
depends=['type', 'quantity']),
2012-09-24 22:18:13 +02:00
'on_change_with_delivery_date')
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@classmethod
def __setup__(cls):
super(SaleLine, cls).__setup__()
cls._order.insert(0, ('sequence', 'ASC'))
cls._error_messages.update({
2013-10-05 12:17:20 +02:00
'customer_location_required': (
'Sale "%(sale)s" is missing the '
'customer location in line "%(line)s".'),
'missing_account_revenue': ('Product "%(product)s" of sale '
'%(sale)s misses a revenue account.'),
'missing_account_revenue_property': ('Sale "%(sale)s" '
'misses an "account revenue" default property.'),
})
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@classmethod
def __register__(cls, module_name):
2013-09-06 22:34:40 +02:00
TableHandler = backend.get('TableHandler')
2010-08-10 11:58:48 -04:00
cursor = Transaction().cursor
2013-09-06 22:34:40 +02:00
sql_table = cls.__table__()
super(SaleLine, cls).__register__(module_name)
2012-09-24 22:18:13 +02:00
table = TableHandler(cursor, cls, module_name)
# Migration from 1.0 comment change into note
2009-02-04 13:31:22 +01:00
if table.column_exist('comment'):
2013-09-06 22:34:40 +02:00
cursor.execute(*sql_table.update(
columns=[sql_table.note],
values=[sql_table.comment]))
2009-02-04 13:31:22 +01:00
table.drop_column('comment', exception=True)
2012-05-14 15:36:50 +02:00
# Migration from 2.4: drop required on sequence
table.not_null_action('sequence', action='remove')
2013-09-06 22:34:40 +02:00
@staticmethod
def order_sequence(tables):
table, _ = tables[None]
return [table.sequence == Null, table.sequence]
2013-09-06 22:34:40 +02:00
2012-09-24 22:18:13 +02:00
@staticmethod
def default_type():
2008-09-03 21:28:12 +02:00
return 'line'
2012-09-24 22:18:13 +02:00
@staticmethod
def default_unit_digits():
return 2
2008-09-03 21:28:12 +02:00
2014-01-26 14:29:31 +01:00
@fields.depends('unit')
2012-09-24 22:18:13 +02:00
def on_change_with_unit_digits(self, name=None):
if self.unit:
return self.unit.digits
2008-09-03 21:28:12 +02:00
return 2
2012-09-24 22:18:13 +02:00
def get_move_done(self, name):
Uom = Pool().get('product.uom')
done = True
if not self.product:
return True
if self.product.type == 'service':
return True
skip_ids = set(x.id for x in self.moves_ignored)
skip_ids.update(x.id for x in self.moves_recreated)
quantity = self.quantity
for move in self.moves:
if move.state != 'done' \
and move.id not in skip_ids:
done = False
break
quantity -= Uom.compute_qty(move.uom, move.quantity, self.unit)
if done:
if quantity > 0.0:
done = False
return done
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
def get_move_exception(self, name):
skip_ids = set(x.id for x in self.moves_ignored)
skip_ids.update(x.id for x in self.moves_recreated)
for move in self.moves:
if move.state == 'cancel' \
and move.id not in skip_ids:
return True
return False
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
def _get_tax_rule_pattern(self):
2009-03-28 19:33:11 +01:00
'''
Get tax rule pattern
'''
2012-09-24 22:18:13 +02:00
return {}
2009-03-28 19:33:11 +01:00
2012-09-24 22:18:13 +02:00
def _get_context_sale_price(self):
2010-08-10 11:58:48 -04:00
context = {}
if getattr(self, 'sale', None):
if getattr(self.sale, 'currency', None):
2012-09-24 22:18:13 +02:00
context['currency'] = self.sale.currency.id
if getattr(self.sale, 'party', None):
2012-09-24 22:18:13 +02:00
context['customer'] = self.sale.party.id
if getattr(self.sale, 'sale_date', None):
2012-09-24 22:18:13 +02:00
context['sale_date'] = self.sale.sale_date
if self.unit:
context['uom'] = self.unit.id
else:
2012-09-24 22:18:13 +02:00
context['uom'] = self.product.sale_uom.id
2010-08-10 11:58:48 -04:00
return context
2014-01-26 14:29:31 +01:00
@fields.depends('product', 'unit', 'quantity', 'description',
'_parent_sale.party', '_parent_sale.currency',
'_parent_sale.sale_date')
2012-09-24 22:18:13 +02:00
def on_change_product(self):
Product = Pool().get('product.product')
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
if not self.product:
2014-10-29 20:40:13 +01:00
return
2008-09-03 21:28:12 +02:00
party = None
2010-08-10 11:58:48 -04:00
party_context = {}
2012-09-24 22:18:13 +02:00
if self.sale and self.sale.party:
party = self.sale.party
2008-09-03 21:28:12 +02:00
if party.lang:
2010-08-10 11:58:48 -04:00
party_context['language'] = party.lang.code
2008-09-03 21:28:12 +02:00
category = self.product.sale_uom.category
if not self.unit or self.unit not in category.uoms:
self.unit = self.product.sale_uom
2014-10-29 20:40:13 +01:00
self.unit_digits = self.product.sale_uom.digits
2012-09-24 22:18:13 +02:00
with Transaction().set_context(self._get_context_sale_price()):
2014-10-29 20:40:13 +01:00
self.unit_price = Product.get_sale_price([self.product],
2012-09-24 22:18:13 +02:00
self.quantity or 0)[self.product.id]
2014-10-29 20:40:13 +01:00
if self.unit_price:
self.unit_price = self.unit_price.quantize(
2012-09-24 22:18:13 +02:00
Decimal(1) / 10 ** self.__class__.unit_price.digits[1])
2014-10-29 20:40:13 +01:00
taxes = []
2012-09-24 22:18:13 +02:00
pattern = self._get_tax_rule_pattern()
for tax in self.product.customer_taxes_used:
2009-03-28 19:33:11 +01:00
if party and party.customer_tax_rule:
2012-09-24 22:18:13 +02:00
tax_ids = party.customer_tax_rule.apply(tax, pattern)
2009-04-06 10:33:38 +02:00
if tax_ids:
2014-10-29 20:40:13 +01:00
taxes.extend(tax_ids)
2009-03-28 19:33:11 +01:00
continue
2014-10-29 20:40:13 +01:00
taxes.append(tax.id)
2009-04-06 10:33:38 +02:00
if party and party.customer_tax_rule:
2012-09-24 22:18:13 +02:00
tax_ids = party.customer_tax_rule.apply(None, pattern)
2009-04-06 10:33:38 +02:00
if tax_ids:
2014-10-29 20:40:13 +01:00
taxes.extend(tax_ids)
self.taxes = taxes
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
if not self.description:
2010-08-10 11:58:48 -04:00
with Transaction().set_context(party_context):
2014-10-29 20:40:13 +01:00
self.description = Product(self.product.id).rec_name
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
self.type = 'line'
2014-10-29 20:40:13 +01:00
self.amount = self.on_change_with_amount()
2008-09-03 21:28:12 +02:00
2014-01-26 14:29:31 +01:00
@fields.depends('product')
2012-09-24 22:18:13 +02:00
def on_change_with_product_uom_category(self, name=None):
if self.product:
return self.product.default_uom_category.id
2012-03-25 15:40:02 +02:00
2014-01-26 14:29:31 +01:00
@fields.depends('product', 'quantity', 'unit',
'_parent_sale.currency', '_parent_sale.party',
'_parent_sale.sale_date')
2012-09-24 22:18:13 +02:00
def on_change_quantity(self):
Product = Pool().get('product.product')
2012-03-25 15:40:02 +02:00
2012-09-24 22:18:13 +02:00
if not self.product:
2014-10-29 20:40:13 +01:00
return
2008-09-03 21:28:12 +02:00
2010-08-10 11:58:48 -04:00
with Transaction().set_context(
2012-09-24 22:18:13 +02:00
self._get_context_sale_price()):
2014-10-29 20:40:13 +01:00
self.unit_price = Product.get_sale_price([self.product],
2012-09-24 22:18:13 +02:00
self.quantity or 0)[self.product.id]
2014-10-29 20:40:13 +01:00
if self.unit_price:
self.unit_price = self.unit_price.quantize(
2012-09-24 22:18:13 +02:00
Decimal(1) / 10 ** self.__class__.unit_price.digits[1])
2008-09-03 21:28:12 +02:00
2014-01-26 14:29:31 +01:00
@fields.depends('product', 'quantity', 'unit',
'_parent_sale.currency', '_parent_sale.party',
'_parent_sale.sale_date')
2012-09-24 22:18:13 +02:00
def on_change_unit(self):
return self.on_change_quantity()
2008-10-03 15:06:29 +02:00
2014-01-26 14:29:31 +01:00
@fields.depends('type', 'quantity', 'unit_price', 'unit',
'_parent_sale.currency')
2012-09-24 22:18:13 +02:00
def on_change_with_amount(self):
if self.type == 'line':
currency = self.sale.currency if self.sale else None
amount = Decimal(str(self.quantity or '0.0')) * \
(self.unit_price or Decimal('0.0'))
if currency:
2012-09-24 22:18:13 +02:00
return currency.round(amount)
return amount
2008-09-03 21:28:12 +02:00
return Decimal('0.0')
2012-09-24 22:18:13 +02:00
def get_amount(self, name):
if self.type == 'line':
2014-09-13 15:24:28 +02:00
return self.on_change_with_amount()
2012-09-24 22:18:13 +02:00
elif self.type == 'subtotal':
amount = Decimal('0.0')
for line2 in self.sale.lines:
if line2.type == 'line':
amount += line2.sale.currency.round(
Decimal(str(line2.quantity)) * line2.unit_price)
elif line2.type == 'subtotal':
if self == line2:
break
amount = Decimal('0.0')
return amount
return Decimal('0.0')
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
def get_warehouse(self, name):
return self.sale.warehouse.id if self.sale.warehouse else None
2011-11-19 19:37:11 +01:00
2012-09-24 22:18:13 +02:00
def get_from_location(self, name):
if self.quantity >= 0:
if self.warehouse:
return self.warehouse.output_location.id
else:
2012-09-24 22:18:13 +02:00
return self.sale.party.customer_location.id
2012-09-24 22:18:13 +02:00
def get_to_location(self, name):
if self.quantity >= 0:
return self.sale.party.customer_location.id
else:
if self.warehouse:
return self.warehouse.input_location.id
2014-01-26 14:29:31 +01:00
@fields.depends('product', 'quantity', '_parent_sale.sale_date')
2012-09-24 22:18:13 +02:00
def on_change_with_delivery_date(self, name=None):
if self.product and self.quantity > 0:
2012-09-24 22:18:13 +02:00
date = self.sale.sale_date if self.sale else None
return self.product.compute_delivery_date(date=date)
2012-09-24 22:18:13 +02:00
def get_invoice_line(self, invoice_type):
2008-09-03 21:28:12 +02:00
'''
2012-09-24 22:18:13 +02:00
Return a list of invoice lines for sale line according to invoice_type
2008-09-03 21:28:12 +02:00
'''
2012-09-24 22:18:13 +02:00
pool = Pool()
Uom = pool.get('product.uom')
Property = pool.get('ir.property')
InvoiceLine = pool.get('account.invoice.line')
2008-09-03 21:28:12 +02:00
2014-09-17 21:45:49 +02:00
invoice_line = InvoiceLine()
2012-09-24 22:18:13 +02:00
invoice_line.type = self.type
invoice_line.description = self.description
invoice_line.note = self.note
2013-09-02 11:19:37 +02:00
invoice_line.origin = self
2012-09-24 22:18:13 +02:00
if self.type != 'line':
if (self.sale.invoice_method == 'order'
2013-09-02 11:19:37 +02:00
and not self.invoice_lines
2012-09-24 22:18:13 +02:00
and ((all(l.quantity >= 0 for l in self.sale.lines
2012-05-30 12:08:26 +02:00
if l.type == 'line')
and invoice_type == 'out_invoice')
2012-09-24 22:18:13 +02:00
or (all(l.quantity <= 0 for l in self.sale.lines
2012-05-30 12:08:26 +02:00
if l.type == 'line')
and invoice_type == 'out_credit_note'))):
2012-09-24 22:18:13 +02:00
return [invoice_line]
2012-04-21 01:19:39 +02:00
else:
return []
2012-09-24 22:18:13 +02:00
if (invoice_type == 'out_invoice') != (self.quantity >= 0):
2012-04-21 01:19:39 +02:00
return []
2009-02-24 15:20:12 +01:00
stock_moves = []
2012-09-24 22:18:13 +02:00
if (self.sale.invoice_method == 'order'
or not self.product
or self.product.type == 'service'):
quantity = abs(self.quantity)
stock_moves = self.moves
2008-09-03 21:28:12 +02:00
else:
quantity = 0.0
2012-09-24 22:18:13 +02:00
for move in self.moves:
2008-09-03 21:28:12 +02:00
if move.state == 'done':
2012-09-24 22:18:13 +02:00
quantity += Uom.compute_qty(move.uom, move.quantity,
self.unit)
if move.invoiced_quantity < move.quantity:
stock_moves.append(move)
if self.sale.shipment_method == 'order':
invoice_line.stock_moves = stock_moves
2009-02-24 15:20:12 +01:00
2012-09-24 22:18:13 +02:00
skip_ids = set(l.id for i in self.sale.invoices_recreated
for l in i.lines)
2012-10-11 17:54:46 +02:00
for old_invoice_line in self.invoice_lines:
if old_invoice_line.type != 'line':
continue
2012-10-11 17:54:46 +02:00
if old_invoice_line.id not in skip_ids:
quantity -= Uom.compute_qty(old_invoice_line.unit,
old_invoice_line.quantity, self.unit)
2009-02-24 15:20:12 +01:00
rounding = self.unit.rounding if self.unit else 0.01
invoice_line.quantity = Uom.round(quantity, rounding)
if invoice_line.quantity <= 0:
2011-05-25 10:12:46 +02:00
return []
2012-09-24 22:18:13 +02:00
invoice_line.unit = self.unit
invoice_line.product = self.product
invoice_line.unit_price = self.unit_price
invoice_line.taxes = self.taxes
2013-05-18 00:13:13 +02:00
invoice_line.invoice_type = invoice_type
2012-09-24 22:18:13 +02:00
if self.product:
invoice_line.account = self.product.account_revenue_used
if not invoice_line.account:
self.raise_user_error('missing_account_revenue', {
'sale': self.sale.rec_name,
'product': self.product.rec_name,
})
2008-09-03 21:28:12 +02:00
else:
for model in ('product.template', 'product.category'):
2012-09-24 22:18:13 +02:00
invoice_line.account = Property.get('account_revenue', model)
if invoice_line.account:
2008-09-03 21:28:12 +02:00
break
2012-09-24 22:18:13 +02:00
if not invoice_line.account:
2013-06-27 21:39:46 +02:00
self.raise_user_error('missing_account_revenue_property', {
'sale': self.sale.rec_name,
})
2012-09-24 22:18:13 +02:00
return [invoice_line]
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
@classmethod
def copy(cls, lines, default=None):
2008-09-03 21:28:12 +02:00
if default is None:
default = {}
default = default.copy()
2012-03-27 10:43:58 +02:00
default['moves'] = None
default['moves_ignored'] = None
default['moves_recreated'] = None
default['invoice_lines'] = None
2012-09-24 22:18:13 +02:00
return super(SaleLine, cls).copy(lines, default=default)
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
def get_move(self, shipment_type):
2008-09-03 21:28:12 +02:00
'''
2013-10-31 00:16:55 +01:00
Return moves for the sale line according to shipment_type
2008-09-03 21:28:12 +02:00
'''
2012-09-24 22:18:13 +02:00
pool = Pool()
Uom = pool.get('product.uom')
Move = pool.get('stock.move')
2008-09-03 21:28:12 +02:00
2012-09-24 22:18:13 +02:00
if self.type != 'line':
2008-09-03 21:28:12 +02:00
return
2012-09-24 22:18:13 +02:00
if not self.product:
2008-09-03 21:28:12 +02:00
return
2012-09-24 22:18:13 +02:00
if self.product.type == 'service':
2008-09-03 21:28:12 +02:00
return
2012-09-24 22:18:13 +02:00
if (shipment_type == 'out') != (self.quantity >= 0):
2012-04-21 01:19:39 +02:00
return
invoice_lines = []
2012-09-24 22:18:13 +02:00
if self.sale.shipment_method == 'order':
quantity = abs(self.quantity)
if self.sale.invoice_method == 'order':
invoice_lines = self.invoice_lines
else:
quantity = 0.0
2012-09-24 22:18:13 +02:00
for invoice_line in self.invoice_lines:
2013-07-12 10:31:05 +02:00
if invoice_line.invoice.state == 'paid':
2012-09-24 22:18:13 +02:00
quantity += Uom.compute_qty(invoice_line.unit,
invoice_line.quantity, self.unit)
if invoice_line.moved_quantity < invoice_line.quantity:
invoice_lines.append(invoice_line)
2012-09-24 22:18:13 +02:00
skip_ids = set(x.id for x in self.moves_recreated)
for move in self.moves:
2009-02-04 14:05:52 +01:00
if move.id not in skip_ids:
2012-09-24 22:18:13 +02:00
quantity -= Uom.compute_qty(move.uom, move.quantity,
self.unit)
quantity = Uom.round(quantity, self.unit.rounding)
if quantity <= 0:
2008-09-03 21:28:12 +02:00
return
2012-09-24 22:18:13 +02:00
if not self.sale.party.customer_location:
self.raise_user_error('customer_location_required', {
'sale': self.sale.rec_name,
'line': self.rec_name,
})
2014-09-17 21:45:49 +02:00
move = Move()
2012-09-24 22:18:13 +02:00
move.quantity = quantity
move.uom = self.unit
move.product = self.product
move.from_location = self.from_location
move.to_location = self.to_location
move.state = 'draft'
move.company = self.sale.company
move.unit_price = self.unit_price
move.currency = self.sale.currency
move.planned_date = self.delivery_date
move.invoice_lines = invoice_lines
move.origin = self
2012-09-24 22:18:13 +02:00
return move
2008-09-03 21:28:12 +02:00
2009-03-05 14:53:56 +01:00
class SaleLineTax(ModelSQL):
'Sale Line - Tax'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.line-account.tax'
2009-03-05 14:53:56 +01:00
_table = 'sale_line_account_tax'
line = fields.Many2One('sale.line', 'Sale Line', ondelete='CASCADE',
2012-03-05 10:53:07 +01:00
select=True, required=True)
2009-03-05 14:53:56 +01:00
tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
2012-03-05 10:53:07 +01:00
select=True, required=True)
2009-03-05 14:53:56 +01:00
class SaleLineIgnoredMove(ModelSQL):
'Sale Line - Ignored Move'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.line-ignored-stock.move'
2009-03-05 14:53:56 +01:00
_table = 'sale_line_moves_ignored_rel'
sale_line = fields.Many2One('sale.line', 'Sale Line', ondelete='CASCADE',
2012-03-05 10:53:07 +01:00
select=True, required=True)
2009-03-05 14:53:56 +01:00
move = fields.Many2One('stock.move', 'Move', ondelete='RESTRICT',
2012-03-05 10:53:07 +01:00
select=True, required=True)
2009-03-05 14:53:56 +01:00
class SaleLineRecreatedMove(ModelSQL):
'Sale Line - Recreated Move'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.line-recreated-stock.move'
2009-03-05 14:53:56 +01:00
_table = 'sale_line_moves_recreated_rel'
sale_line = fields.Many2One('sale.line', 'Sale Line', ondelete='CASCADE',
2012-03-05 10:53:07 +01:00
select=True, required=True)
2009-03-05 14:53:56 +01:00
move = fields.Many2One('stock.move', 'Move', ondelete='RESTRICT',
2012-03-05 10:53:07 +01:00
select=True, required=True)
2009-03-05 14:53:56 +01:00
2008-09-03 21:28:12 +02:00
class SaleReport(CompanyReport):
2012-09-24 22:18:13 +02:00
__name__ = 'sale.sale'
2008-09-03 21:28:12 +02:00
class OpenCustomer(Wizard):
'Open Customers'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.open_customer'
2012-01-02 18:04:22 +01:00
start_state = 'open_'
open_ = StateAction('party.act_party_form')
2012-09-24 22:18:13 +02:00
def do_open_(self, action):
2011-07-03 13:19:11 +02:00
pool = Pool()
2012-09-24 22:18:13 +02:00
ModelData = pool.get('ir.model.data')
Wizard = pool.get('ir.action.wizard')
2013-09-06 22:34:40 +02:00
Sale = pool.get('sale.sale')
2012-09-24 22:18:13 +02:00
cursor = Transaction().cursor
2013-09-06 22:34:40 +02:00
sale = Sale.__table__()
cursor.execute(*sale.select(sale.party, group_by=sale.party))
2010-08-10 11:58:48 -04:00
customer_ids = [line[0] for line in Transaction().cursor.fetchall()]
2012-01-02 18:04:22 +01:00
action['pyson_domain'] = PYSONEncoder().encode(
[('id', 'in', customer_ids)])
2012-09-24 22:18:13 +02:00
model_data, = ModelData.search([
('fs_id', '=', 'act_open_customer'),
('module', '=', 'sale'),
], limit=1)
wizard = Wizard(model_data.db_id)
2012-01-02 18:04:22 +01:00
action['name'] = wizard.name
return action, {}
2012-09-24 22:18:13 +02:00
def transition_open_(self):
2012-01-02 18:04:22 +01:00
return 'end'
class HandleShipmentExceptionAsk(ModelView):
2012-01-02 18:04:22 +01:00
'Handle Shipment Exception'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.handle.shipment.exception.ask'
2009-02-24 16:39:55 +01:00
recreate_moves = fields.Many2Many(
2009-03-05 14:53:56 +01:00
'stock.move', None, None, 'Recreate Moves',
2010-01-20 11:03:54 +01:00
domain=[('id', 'in', Eval('domain_moves'))], depends=['domain_moves'])
2009-02-04 14:05:52 +01:00
domain_moves = fields.Many2Many(
2009-03-05 14:53:56 +01:00
'stock.move', None, None, 'Domain Moves')
2009-02-04 14:05:52 +01:00
2012-09-24 22:18:13 +02:00
@classmethod
def __register__(cls, module_name):
2010-08-10 11:58:48 -04:00
cursor = Transaction().cursor
2013-09-06 22:34:40 +02:00
model = Table('ir_model')
# Migration from 1.2: packing renamed into shipment
2013-09-06 22:34:40 +02:00
cursor.execute(*model.update(
columns=[model.model],
values=[Overlay(model.model, 'shipment',
Position('packing', model.model),
len('packing'))],
where=model.model.like('%packing%')
& (model.module == module_name)))
2012-09-24 22:18:13 +02:00
super(HandleShipmentExceptionAsk, cls).__register__(module_name)
2009-02-04 14:05:52 +01:00
2012-01-02 18:04:22 +01:00
class HandleShipmentException(Wizard):
2009-03-20 12:12:34 +01:00
'Handle Shipment Exception'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.handle.shipment.exception'
2012-01-02 18:04:22 +01:00
start_state = 'ask'
ask = StateView('sale.handle.shipment.exception.ask',
'sale.handle_shipment_exception_ask_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'handle', 'tryton-ok', default=True),
])
handle = StateTransition()
2009-02-04 14:05:52 +01:00
2012-09-24 22:18:13 +02:00
def default_ask(self, fields):
Sale = Pool().get('sale.sale')
sale = Sale(Transaction().context.get('active_id'))
2012-01-02 18:04:22 +01:00
moves = []
for line in sale.lines:
2012-09-24 22:18:13 +02:00
skips = set(line.moves_ignored)
skips.update(line.moves_recreated)
2012-01-02 18:04:22 +01:00
for move in line.moves:
2012-09-24 22:18:13 +02:00
if move.state == 'cancel' and move not in skips:
2012-01-02 18:04:22 +01:00
moves.append(move.id)
return {
'recreate_moves': moves,
'domain_moves': moves,
}
2012-09-24 22:18:13 +02:00
def transition_handle(self):
2011-07-03 13:19:11 +02:00
pool = Pool()
2012-09-24 22:18:13 +02:00
Sale = pool.get('sale.sale')
SaleLine = pool.get('sale.line')
2009-02-04 14:05:52 +01:00
2012-09-24 22:18:13 +02:00
sale = Sale(Transaction().context['active_id'])
2009-02-04 14:05:52 +01:00
for line in sale.lines:
moves_ignored = []
2009-02-24 16:39:55 +01:00
moves_recreated = []
2012-09-24 22:18:13 +02:00
skips = set(line.moves_ignored)
skips.update(line.moves_recreated)
2009-02-04 14:05:52 +01:00
for move in line.moves:
2012-09-24 22:18:13 +02:00
if move not in self.ask.domain_moves or move in skips:
2009-02-04 14:05:52 +01:00
continue
2012-09-24 22:18:13 +02:00
if move in self.ask.recreate_moves:
2009-02-24 16:39:55 +01:00
moves_recreated.append(move.id)
2009-02-04 14:05:52 +01:00
else:
moves_ignored.append(move.id)
2012-09-24 22:18:13 +02:00
SaleLine.write([line], {
2012-04-03 20:07:35 +02:00
'moves_ignored': [('add', moves_ignored)],
'moves_recreated': [('add', moves_recreated)],
})
2012-09-24 22:18:13 +02:00
Sale.process([sale])
2012-02-26 18:41:57 +01:00
return 'end'
2009-02-04 14:05:52 +01:00
2009-02-10 12:49:11 +01:00
2009-02-28 17:04:17 +01:00
class HandleInvoiceExceptionAsk(ModelView):
2012-01-02 18:04:22 +01:00
'Handle Invoice Exception'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.handle.invoice.exception.ask'
2009-02-24 15:20:12 +01:00
recreate_invoices = fields.Many2Many(
2009-03-05 14:53:56 +01:00
'account.invoice', None, None, 'Recreate Invoices',
2010-01-20 11:03:54 +01:00
domain=[('id', 'in', Eval('domain_invoices'))],
depends=['domain_invoices'],
2010-08-10 11:58:48 -04:00
help='The selected invoices will be recreated. '
2014-12-02 12:55:52 +01:00
'The other ones will be ignored.')
2009-02-10 12:49:11 +01:00
domain_invoices = fields.Many2Many(
2009-03-05 14:53:56 +01:00
'account.invoice', None, None, 'Domain Invoices')
2009-02-10 12:49:11 +01:00
class HandleInvoiceException(Wizard):
'Handle Invoice Exception'
2012-09-24 22:18:13 +02:00
__name__ = 'sale.handle.invoice.exception'
2012-01-02 18:04:22 +01:00
start_state = 'ask'
ask = StateView('sale.handle.invoice.exception.ask',
'sale.handle_invoice_exception_ask_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'handle', 'tryton-ok', default=True),
])
handle = StateTransition()
2009-02-10 12:49:11 +01:00
2012-09-24 22:18:13 +02:00
def default_ask(self, fields):
Sale = Pool().get('sale.sale')
2009-02-10 12:49:11 +01:00
2012-09-24 22:18:13 +02:00
sale = Sale(Transaction().context['active_id'])
skips = set(sale.invoices_ignored)
skips.update(sale.invoices_recreated)
2012-01-02 18:04:22 +01:00
invoices = []
for invoice in sale.invoices:
2012-09-24 22:18:13 +02:00
if invoice.state == 'cancel' and invoice not in skips:
2012-01-02 18:04:22 +01:00
invoices.append(invoice.id)
return {
2012-09-04 22:53:48 +02:00
'recreate_invoices': invoices,
2012-01-02 18:04:22 +01:00
'domain_invoices': invoices,
}
2012-09-24 22:18:13 +02:00
def transition_handle(self):
Sale = Pool().get('sale.sale')
2012-01-02 18:04:22 +01:00
2012-09-24 22:18:13 +02:00
sale = Sale(Transaction().context['active_id'])
2009-02-10 12:49:11 +01:00
2012-09-24 22:18:13 +02:00
skips = set(sale.invoices_ignored)
skips.update(sale.invoices_recreated)
2009-02-10 12:49:11 +01:00
invoices_ignored = []
2009-02-24 15:20:12 +01:00
invoices_recreated = []
2009-02-10 12:49:11 +01:00
for invoice in sale.invoices:
2012-09-24 22:18:13 +02:00
if invoice not in self.ask.domain_invoices or invoice in skips:
2009-02-10 12:49:11 +01:00
continue
2012-09-24 22:18:13 +02:00
if invoice in self.ask.recreate_invoices:
2009-02-24 15:20:12 +01:00
invoices_recreated.append(invoice.id)
2009-02-10 12:49:11 +01:00
else:
invoices_ignored.append(invoice.id)
2012-09-24 22:18:13 +02:00
Sale.write([sale], {
2012-04-03 20:07:35 +02:00
'invoices_ignored': [('add', invoices_ignored)],
'invoices_recreated': [('add', invoices_recreated)],
})
2012-09-24 22:18:13 +02:00
Sale.process([sale])
2012-02-26 18:41:57 +01:00
return 'end'
2009-02-10 12:49:11 +01:00
2012-04-02 12:48:09 +02:00
class ReturnSaleStart(ModelView):
'Return Sale'
__name__ = 'sale.return_sale.start'
2012-04-02 12:48:09 +02:00
class ReturnSale(Wizard):
'Return Sale'
__name__ = 'sale.return_sale'
start = StateView('sale.return_sale.start',
'sale.return_sale_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Return', 'return_', 'tryton-ok', default=True),
])
return_ = StateAction('sale.act_sale_form')
def do_return_(self, action):
2012-09-24 22:18:13 +02:00
Sale = Pool().get('sale.sale')
2012-04-02 12:48:09 +02:00
sales = Sale.browse(Transaction().context['active_ids'])
return_sales = Sale.copy(sales)
for sale in return_sales:
for line in sale.lines:
if line.type == 'line':
line.quantity *= -1
line.save()
data = {'res_id': [s.id for s in return_sales]}
if len(return_sales) == 1:
action['views'].reverse()
return action, data