788 lines
28 KiB
Python
Executable File
788 lines
28 KiB
Python
Executable File
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
|
|
from collections import defaultdict
|
|
from decimal import Decimal
|
|
|
|
from sql import Null
|
|
from sql.functions import CharLength
|
|
|
|
from trytond.i18n import gettext
|
|
from trytond.model import (
|
|
DeactivableMixin, Index, ModelSQL, ModelView, Workflow, fields)
|
|
from trytond.model.exceptions import AccessError
|
|
from trytond.modules.company.model import (
|
|
employee_field, reset_employee, set_employee)
|
|
from trytond.modules.currency.fields import Monetary
|
|
from trytond.modules.product import price_digits
|
|
from trytond.pool import Pool
|
|
from trytond.pyson import Bool, Eval, If
|
|
from trytond.transaction import Transaction
|
|
|
|
|
|
class Type(DeactivableMixin, ModelSQL, ModelView):
|
|
'Customer Complaint Type'
|
|
__name__ = 'sale.complaint.type'
|
|
|
|
name = fields.Char('Name', required=True)
|
|
origin = fields.Many2One('ir.model', 'Origin', required=True,
|
|
domain=[('model', 'in', ['sale.sale', 'sale.line',
|
|
'account.invoice', 'account.invoice.line'])])
|
|
|
|
|
|
class Complaint(Workflow, ModelSQL, ModelView):
|
|
'Customer Complaint'
|
|
__name__ = 'sale.complaint'
|
|
_rec_name = 'number'
|
|
|
|
_states = {
|
|
'readonly': Eval('state') != 'draft',
|
|
}
|
|
|
|
number = fields.Char("Number", readonly=True)
|
|
reference = fields.Char("Reference")
|
|
date = fields.Date('Date', states=_states)
|
|
customer = fields.Many2One(
|
|
'party.party', "Customer", required=True, states=_states,
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends={'company'})
|
|
company = fields.Many2One(
|
|
'company.company', 'Company', required=True,
|
|
states={
|
|
'readonly': _states['readonly'] | Eval('origin'),
|
|
})
|
|
type = fields.Many2One('sale.complaint.type', 'Type', required=True,
|
|
states=_states)
|
|
origin = fields.Reference('Origin', selection='get_origin',
|
|
domain={
|
|
'sale.sale': [
|
|
If(Eval('customer'),
|
|
('party', '=', Eval('customer')),
|
|
()),
|
|
('company', '=', Eval('company')),
|
|
('state', 'in', ['confirmed', 'processing', 'done']),
|
|
],
|
|
'sale.line': [
|
|
('type', '=', 'line'),
|
|
If(Eval('customer'),
|
|
('sale.party', '=', Eval('customer')),
|
|
()),
|
|
('sale.company', '=', Eval('company')),
|
|
('sale.state', 'in', ['confirmed', 'processing', 'done']),
|
|
],
|
|
'account.invoice': [
|
|
If(Eval('customer'),
|
|
('party', '=', Eval('customer')),
|
|
()),
|
|
('company', '=', Eval('company')),
|
|
('type', '=', 'out'),
|
|
('state', 'in', ['posted', 'paid']),
|
|
],
|
|
'account.invoice.line': [
|
|
('type', '=', 'line'),
|
|
If(Eval('customer'),
|
|
('invoice.party', '=', Eval('customer')),
|
|
()),
|
|
('invoice.company', '=', Eval('company')),
|
|
('invoice.type', '=', 'out'),
|
|
('invoice.state', 'in', ['posted', 'paid']),
|
|
],
|
|
},
|
|
states={
|
|
'readonly': ((Eval('state') != 'draft')
|
|
| Bool(Eval('actions', [0]))),
|
|
'required': Bool(Eval('origin_model')),
|
|
},
|
|
depends={'origin_model'})
|
|
origin_id = fields.Function(fields.Integer('Origin ID'),
|
|
'on_change_with_origin_id')
|
|
origin_model = fields.Function(fields.Char('Origin Model'),
|
|
'on_change_with_origin_model')
|
|
description = fields.Text('Description', states=_states)
|
|
actions = fields.One2Many('sale.complaint.action', 'complaint', 'Actions',
|
|
states={
|
|
'readonly': ((Eval('state') != 'draft')
|
|
| (If(~Eval('origin_id', 0), 0, Eval('origin_id', 0)) <= 0)),
|
|
},
|
|
depends={'origin_model'})
|
|
submitted_by = employee_field(
|
|
"Submitted By",
|
|
states=['waiting', 'approved', 'rejected', 'done', 'cancelled'])
|
|
approved_by = employee_field(
|
|
"Approved By",
|
|
states=['approved', 'rejected', 'done', 'cancelled'])
|
|
rejected_by = employee_field(
|
|
"Rejected By",
|
|
states=['approved', 'rejected', 'done', 'cancelled'])
|
|
cancelled_by = employee_field(
|
|
"Cancelled By",
|
|
states=['cancelled'])
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('waiting', 'Waiting'),
|
|
('approved', 'Approved'),
|
|
('rejected', 'Rejected'),
|
|
('done', 'Done'),
|
|
('cancelled', 'Cancelled'),
|
|
], "State", readonly=True, required=True, sort=False)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
cls.number.search_unaccented = False
|
|
cls.reference.search_unaccented = False
|
|
super(Complaint, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_indexes.update({
|
|
Index(t, (t.reference, Index.Similarity())),
|
|
Index(
|
|
t,
|
|
(t.state, Index.Equality()),
|
|
where=t.state.in_(['draft', 'waiting', 'approved'])),
|
|
})
|
|
cls._order.insert(0, ('date', 'DESC'))
|
|
cls._transitions |= set((
|
|
('draft', 'waiting'),
|
|
('waiting', 'draft'),
|
|
('waiting', 'approved'),
|
|
('waiting', 'rejected'),
|
|
('approved', 'done'),
|
|
('approved', 'draft'),
|
|
('draft', 'cancelled'),
|
|
('waiting', 'cancelled'),
|
|
('done', 'draft'),
|
|
('rejected', 'draft'),
|
|
('cancelled', 'draft'),
|
|
))
|
|
cls._buttons.update({
|
|
'cancel': {
|
|
'invisible': ~Eval('state').in_(['draft', 'waiting']),
|
|
'depends': ['state'],
|
|
},
|
|
'draft': {
|
|
'invisible': ~Eval('state').in_(
|
|
['waiting', 'done', 'cancelled']),
|
|
'icon': If(Eval('state').in_(['done', 'cancelled']),
|
|
'tryton-undo', 'tryton-back'),
|
|
'depends': ['state'],
|
|
},
|
|
'wait': {
|
|
'invisible': ~Eval('state').in_(['draft']),
|
|
'depends': ['state'],
|
|
},
|
|
'approve': {
|
|
'invisible': ~Eval('state').in_(['waiting']),
|
|
'depends': ['state'],
|
|
},
|
|
'reject': {
|
|
'invisible': ~Eval('state').in_(['waiting']),
|
|
'depends': ['state'],
|
|
},
|
|
'process': {
|
|
'invisible': ~Eval('state').in_(['approved']),
|
|
'depends': ['state'],
|
|
},
|
|
})
|
|
|
|
actions_domains = cls._actions_domains()
|
|
actions_domain = [('action', 'in', actions_domains.pop(None))]
|
|
for model, actions in actions_domains.items():
|
|
actions_domain = If(Eval('origin_model') == model,
|
|
[('action', 'in', actions)], actions_domain)
|
|
cls.actions.domain = [actions_domain]
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
table_h = cls.__table_handler__(module_name)
|
|
|
|
# Migration from 6.4: rename employee into submitted_by
|
|
if (table_h.column_exist('employee')
|
|
and not table_h.column_exist('submitted_by')):
|
|
table_h.column_rename('employee', 'submitted_by')
|
|
|
|
super(Complaint, cls).__register__(module_name)
|
|
|
|
@classmethod
|
|
def _actions_domains(cls):
|
|
return {
|
|
None: [],
|
|
'sale.sale': ['sale_return'],
|
|
'sale.line': ['sale_return'],
|
|
'account.invoice': ['credit_note'],
|
|
'account.invoice.line': ['credit_note'],
|
|
}
|
|
|
|
@classmethod
|
|
def order_number(cls, tables):
|
|
table, _ = tables[None]
|
|
return [
|
|
~((table.state == 'cancelled') & (table.number == Null)),
|
|
CharLength(table.number), table.number]
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
pool = Pool()
|
|
Date = pool.get('ir.date')
|
|
return Date.today()
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@fields.depends('type')
|
|
def get_origin(self):
|
|
if self.type:
|
|
origin = self.type.origin
|
|
return [('', ''), (origin.model, origin.name)]
|
|
else:
|
|
return []
|
|
|
|
@fields.depends('origin', 'customer')
|
|
def on_change_origin(self):
|
|
pool = Pool()
|
|
Sale = pool.get('sale.sale')
|
|
SaleLine = pool.get('sale.line')
|
|
Invoice = pool.get('account.invoice')
|
|
InvoiceLine = pool.get('account.invoice.line')
|
|
if not self.customer and self.origin and self.origin.id >= 0:
|
|
if isinstance(self.origin, Sale):
|
|
self.customer = self.origin.party
|
|
elif isinstance(self.origin, SaleLine):
|
|
self.customer = self.origin.sale.party
|
|
elif isinstance(self.origin, Invoice):
|
|
self.customer = self.origin.party
|
|
elif isinstance(self.origin, InvoiceLine) and self.origin.invoice:
|
|
self.customer = self.origin.invoice.party
|
|
|
|
@fields.depends('origin')
|
|
def on_change_with_origin_id(self, name=None):
|
|
if self.origin:
|
|
return self.origin.id
|
|
|
|
@fields.depends('origin')
|
|
def on_change_with_origin_model(self, name=None):
|
|
if self.origin:
|
|
return self.origin.__class__.__name__
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return super().view_attributes() + [
|
|
('/tree', 'visual', If(Eval('state') == 'cancelled', 'muted', '')),
|
|
]
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
pool = Pool()
|
|
Configuration = pool.get('sale.configuration')
|
|
|
|
config = Configuration(1)
|
|
vlist = [v.copy() for v in vlist]
|
|
default_company = cls.default_company()
|
|
for values in vlist:
|
|
if values.get('number') is None:
|
|
values['number'] = config.get_multivalue(
|
|
'complaint_sequence',
|
|
company=values.get('company', default_company)).get()
|
|
return super(Complaint, cls).create(vlist)
|
|
|
|
@classmethod
|
|
def copy(cls, complaints, default=None):
|
|
if default is None:
|
|
default = {}
|
|
else:
|
|
default = default.copy()
|
|
default.setdefault('number', None)
|
|
default.setdefault('submitted_by')
|
|
default.setdefault('approved_by')
|
|
default.setdefault('rejected_by')
|
|
default.setdefault('cancelled_by')
|
|
return super(Complaint, cls).copy(complaints, default=default)
|
|
|
|
@classmethod
|
|
def delete(cls, complaints):
|
|
for complaint in complaints:
|
|
if complaint.state != 'draft':
|
|
raise AccessError(
|
|
gettext('sale_complaint.msg_complaint_delete_draft',
|
|
complaint=complaint.rec_name))
|
|
super(Complaint, cls).delete(complaints)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('cancelled')
|
|
@set_employee('cancelled_by')
|
|
def cancel(cls, complaints):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('draft')
|
|
@reset_employee(
|
|
'submitted_by', 'approved_by', 'rejected_by', 'cancelled_by')
|
|
def draft(cls, complaints):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('waiting')
|
|
@set_employee('submitted_by')
|
|
def wait(cls, complaints):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('approved')
|
|
@set_employee('approved_by')
|
|
def approve(cls, complaints):
|
|
pool = Pool()
|
|
Configuration = pool.get('sale.configuration')
|
|
transaction = Transaction()
|
|
context = transaction.context
|
|
config = Configuration(1)
|
|
with transaction.set_context(
|
|
queue_scheduled_at=config.sale_process_after,
|
|
queue_batch=context.get('queue_batch', True)):
|
|
cls.__queue__.process(complaints)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('rejected')
|
|
@set_employee('rejected_by')
|
|
def reject(cls, complaints):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('done')
|
|
def process(cls, complaints):
|
|
pool = Pool()
|
|
Action = pool.get('sale.complaint.action')
|
|
results = defaultdict(list)
|
|
actions = defaultdict(list)
|
|
for complaint in complaints:
|
|
for action in complaint.actions:
|
|
if action.result:
|
|
continue
|
|
result = action.do()
|
|
results[result.__class__].append(result)
|
|
actions[result.__class__].append(action)
|
|
for kls, records in results.items():
|
|
kls.save(records)
|
|
for action, record in zip(actions[kls], records):
|
|
action.result = record
|
|
Action.save(sum(list(actions.values()), []))
|
|
|
|
|
|
class Action(ModelSQL, ModelView):
|
|
'Customer Complaint Action'
|
|
__name__ = 'sale.complaint.action'
|
|
|
|
_states = {
|
|
'readonly': ((Eval('complaint_state') != 'draft')
|
|
| Bool(Eval('result'))),
|
|
}
|
|
_line_states = {
|
|
'invisible': ~Eval('_parent_complaint', {}
|
|
).get('origin_model', 'sale.line').in_(
|
|
['sale.line', 'account.invoice.line']),
|
|
'readonly': _states['readonly'],
|
|
}
|
|
|
|
complaint = fields.Many2One(
|
|
'sale.complaint', 'Complaint', required=True, ondelete='CASCADE',
|
|
states=_states)
|
|
action = fields.Selection([
|
|
('sale_return', 'Create Sale Return'),
|
|
('credit_note', 'Create Credit Note'),
|
|
], 'Action', states=_states)
|
|
|
|
sale_lines = fields.One2Many(
|
|
'sale.complaint.action-sale.line', 'action', "Sale Lines",
|
|
states={
|
|
'invisible': Eval('_parent_complaint', {}
|
|
).get('origin_model', 'sale.sale') != 'sale.sale',
|
|
'readonly': _states['readonly'],
|
|
},
|
|
help='Leave empty for all lines.')
|
|
|
|
invoice_lines = fields.One2Many(
|
|
'sale.complaint.action-account.invoice.line', 'action',
|
|
"Invoice Lines",
|
|
states={
|
|
'invisible': Eval('_parent_complaint', {}
|
|
).get('origin_model', 'account.invoice.line'
|
|
) != 'account.invoice',
|
|
'readonly': _states['readonly'],
|
|
},
|
|
help='Leave empty for all lines.')
|
|
|
|
quantity = fields.Float(
|
|
"Quantity", digits='unit',
|
|
states=_line_states,
|
|
help='Leave empty for the same quantity.')
|
|
unit = fields.Function(fields.Many2One('product.uom', 'Unit',
|
|
states=_line_states),
|
|
'on_change_with_unit')
|
|
unit_price = Monetary(
|
|
"Unit Price", currency='currency', digits=price_digits,
|
|
states=_line_states,
|
|
help='Leave empty for the same price.')
|
|
|
|
amount = fields.Function(Monetary(
|
|
"Amount", 'currency', digits='currency'),
|
|
'on_change_with_amount')
|
|
currency = fields.Function(fields.Many2One(
|
|
'currency.currency', "Currency"),
|
|
'on_change_with_currency')
|
|
|
|
result = fields.Reference('Result', selection='get_result', readonly=True)
|
|
|
|
complaint_state = fields.Function(
|
|
fields.Selection('get_complaint_states', "Complaint State"),
|
|
'on_change_with_complaint_state')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls.__access__.add('complaint')
|
|
|
|
@fields.depends('complaint',
|
|
'_parent_complaint.origin_model', '_parent_complaint.origin')
|
|
def on_change_with_unit(self, name=None):
|
|
if (self.complaint
|
|
and self.complaint.origin_model in {
|
|
'sale.line', 'account.invoice.line'}):
|
|
return self.complaint.origin.unit
|
|
|
|
@fields.depends(
|
|
'quantity', 'unit_price', 'currency', 'sale_lines', 'invoice_lines',
|
|
'complaint', '_parent_complaint.origin_model',
|
|
'_parent_complaint.origin')
|
|
def on_change_with_amount(self, name=None):
|
|
if self.complaint:
|
|
if self.complaint.origin_model in {
|
|
'sale.line', 'account.invoice.line'}:
|
|
if self.quantity is not None:
|
|
quantity = self.quantity
|
|
else:
|
|
quantity = self.complaint.origin.quantity
|
|
if self.unit_price is not None:
|
|
unit_price = self.unit_price
|
|
else:
|
|
unit_price = self.complaint.origin.unit_price
|
|
amount = Decimal(str(quantity)) * unit_price
|
|
if self.currency:
|
|
amount = self.currency.round(amount)
|
|
return amount
|
|
elif self.complaint.origin_model == 'sale.sale':
|
|
if not self.sale_lines:
|
|
if self.complaint and self.complaint.origin:
|
|
return self.complaint.origin.untaxed_amount
|
|
else:
|
|
return sum(
|
|
getattr(l, 'amount', None) or Decimal(0)
|
|
for l in self.sale_lines)
|
|
elif self.complaint.origin_model == 'account.invoice':
|
|
if not self.invoice_lines:
|
|
if self.complaint and self.complaint.origin:
|
|
return self.complaint.origin.untaxed_amount
|
|
else:
|
|
return sum(
|
|
getattr(l, 'amount', None) or Decimal(0)
|
|
for l in self.invoice_lines)
|
|
|
|
@fields.depends(
|
|
'complaint',
|
|
'_parent_complaint.origin_model', '_parent_complaint.origin')
|
|
def on_change_with_currency(self, name=None):
|
|
if (self.complaint
|
|
and self.complaint.origin_model in {
|
|
'sale.sale', 'sale.line',
|
|
'account.invoice', 'account.invoice.line'}):
|
|
return self.complaint.origin.currency
|
|
|
|
@classmethod
|
|
def get_complaint_states(cls):
|
|
pool = Pool()
|
|
Complaint = pool.get('sale.complaint')
|
|
return Complaint.fields_get(['state'])['state']['selection']
|
|
|
|
@fields.depends('complaint', '_parent_complaint.state')
|
|
def on_change_with_complaint_state(self, name=None):
|
|
if self.complaint:
|
|
return self.complaint.state
|
|
|
|
@classmethod
|
|
def _get_result(cls):
|
|
'Return list of Model names for result Reference'
|
|
return ['sale.sale', 'account.invoice']
|
|
|
|
@classmethod
|
|
def get_result(cls):
|
|
pool = Pool()
|
|
Model = pool.get('ir.model')
|
|
get_name = Model.get_name
|
|
models = cls._get_result()
|
|
return [(None, '')] + [(m, get_name(m)) for m in models]
|
|
|
|
@classmethod
|
|
def copy(cls, actions, default=None):
|
|
if default is None:
|
|
default = {}
|
|
else:
|
|
default = default.copy()
|
|
default.setdefault('result', None)
|
|
return super().copy(actions, default=default)
|
|
|
|
def do(self):
|
|
return getattr(self, 'do_%s' % self.action)()
|
|
|
|
def do_sale_return(self):
|
|
pool = Pool()
|
|
Sale = pool.get('sale.sale')
|
|
Line = pool.get('sale.line')
|
|
|
|
if isinstance(self.complaint.origin, (Sale, Line)):
|
|
default = {}
|
|
if isinstance(self.complaint.origin, Sale):
|
|
sale = self.complaint.origin
|
|
if self.sale_lines:
|
|
sale_lines = [l.line for l in self.sale_lines]
|
|
line2qty = {
|
|
l.line.id: l.get_quantity() for l in self.sale_lines}
|
|
line2price = {
|
|
l.line.id: l.get_unit_price() for l in self.sale_lines}
|
|
default['quantity'] = lambda o: line2qty.get(o['id'])
|
|
default['unit_price'] = lambda o: line2price.get(o['id'])
|
|
else:
|
|
sale_lines = [l for l in sale.lines if l.type == 'line']
|
|
elif isinstance(self.complaint.origin, Line):
|
|
sale_line = self.complaint.origin
|
|
sale = sale_line.sale
|
|
sale_lines = [sale_line]
|
|
if self.quantity is not None:
|
|
default['quantity'] = self.quantity
|
|
if self.unit_price is not None:
|
|
default['unit_price'] = self.unit_price
|
|
return_sale, = Sale.copy([sale], default={'lines': None})
|
|
default['sale'] = return_sale.id
|
|
Line.copy(sale_lines, default=default)
|
|
else:
|
|
return
|
|
return_sale.origin = self.complaint
|
|
for line in return_sale.lines:
|
|
if line.type == 'line':
|
|
line.quantity *= -1
|
|
return_sale.lines = return_sale.lines # Force saving
|
|
return return_sale
|
|
|
|
def do_credit_note(self):
|
|
pool = Pool()
|
|
Invoice = pool.get('account.invoice')
|
|
Line = pool.get('account.invoice.line')
|
|
|
|
if isinstance(self.complaint.origin, (Invoice, Line)):
|
|
line2qty = line2price = {}
|
|
if isinstance(self.complaint.origin, Invoice):
|
|
invoice = self.complaint.origin
|
|
if self.invoice_lines:
|
|
invoice_lines = [l.line for l in self.invoice_lines]
|
|
line2qty = {l.line: l.quantity
|
|
for l in self.invoice_lines
|
|
if l.quantity is not None}
|
|
line2price = {l.line: l.unit_price
|
|
for l in self.invoice_lines
|
|
if l.unit_price is not None}
|
|
else:
|
|
invoice_lines = [
|
|
l for l in invoice.lines if l.type == 'line']
|
|
elif isinstance(self.complaint.origin, Line):
|
|
invoice_line = self.complaint.origin
|
|
invoice = invoice_line.invoice
|
|
invoice_lines = [invoice_line]
|
|
if self.quantity is not None:
|
|
line2qty = {invoice_line: self.quantity}
|
|
if self.unit_price is not None:
|
|
line2price = {invoice_line: self.unit_price}
|
|
with Transaction().set_context(_account_invoice_correction=True):
|
|
credit_note, = Invoice.copy([invoice], default={
|
|
'lines': [],
|
|
'taxes': [],
|
|
})
|
|
# Copy each line one by one to get negative and positive lines
|
|
# following each other
|
|
for invoice_line in invoice_lines:
|
|
qty = line2qty.get(invoice_line, invoice_line.quantity)
|
|
unit_price = invoice_line.unit_price - line2price.get(
|
|
invoice_line, invoice_line.unit_price)
|
|
Line.copy([invoice_line], default={
|
|
'invoice': credit_note.id,
|
|
'quantity': -qty,
|
|
'origin': str(self.complaint),
|
|
})
|
|
credit_line, = Line.copy([invoice_line], default={
|
|
'invoice': credit_note.id,
|
|
'quantity': qty,
|
|
'unit_price': unit_price,
|
|
'origin': str(self.complaint),
|
|
})
|
|
credit_note.update_taxes()
|
|
else:
|
|
return
|
|
return credit_note
|
|
|
|
@classmethod
|
|
def delete(cls, actions):
|
|
for action in actions:
|
|
if action.result:
|
|
raise AccessError(
|
|
gettext('sale_complaint.msg_action_delete_result',
|
|
action=action.rec_name))
|
|
super(Action, cls).delete(actions)
|
|
|
|
|
|
class _Action_Line:
|
|
__slots__ = ()
|
|
_states = {
|
|
'readonly': (
|
|
(Eval('complaint_state') != 'draft')
|
|
| Bool(Eval('_parent_action.result', True))),
|
|
}
|
|
|
|
action = fields.Many2One(
|
|
'sale.complaint.action', "Action", ondelete='CASCADE', required=True)
|
|
quantity = fields.Float(
|
|
"Quantity", digits='unit', states=_states)
|
|
unit = fields.Function(
|
|
fields.Many2One('product.uom', "Unit"), 'on_change_with_unit')
|
|
unit_price = Monetary(
|
|
"Unit Price", currency='currency', digits=price_digits,
|
|
states=_states,
|
|
help='Leave empty for the same price.')
|
|
|
|
amount = fields.Function(Monetary(
|
|
"Amount", currency='currency', digits='currency'),
|
|
'on_change_with_amount')
|
|
currency = fields.Function(fields.Many2One(
|
|
'currency.currency', "Currency"),
|
|
'on_change_with_currency')
|
|
|
|
complaint_state = fields.Function(
|
|
fields.Selection('get_complaint_states', "Complaint State"),
|
|
'on_change_with_complaint_state')
|
|
complaint_origin_id = fields.Function(
|
|
fields.Integer("Complaint Origin ID"),
|
|
'on_change_with_complaint_origin_id')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls.__access__.add('action')
|
|
|
|
def on_change_with_unit(self, name=None):
|
|
raise NotImplementedError
|
|
|
|
@fields.depends('currency', methods=['get_quantity', 'get_unit_price'])
|
|
def on_change_with_amount(self, name=None):
|
|
quantity = self.get_quantity() or 0
|
|
unit_price = self.get_unit_price() or Decimal(0)
|
|
amount = Decimal(str(quantity)) * unit_price
|
|
if self.currency:
|
|
amount = self.currency.round(amount)
|
|
return amount
|
|
|
|
def get_quantity(self):
|
|
raise NotImplementedError
|
|
|
|
def get_unit_price(self):
|
|
raise NotImplementedError
|
|
|
|
@fields.depends('action', '_parent_action.currency')
|
|
def on_change_with_currency(self, name=None):
|
|
return self.action.currency if self.action else None
|
|
|
|
@classmethod
|
|
def get_complaint_states(cls):
|
|
pool = Pool()
|
|
Complaint = pool.get('sale.complaint')
|
|
return Complaint.fields_get(['state'])['state']['selection']
|
|
|
|
@fields.depends('action', '_parent_action.complaint',
|
|
'_parent_action._parent_complaint.state')
|
|
def on_change_with_complaint_state(self, name=None):
|
|
if self.action and self.action.complaint:
|
|
return self.action.complaint.state
|
|
|
|
@fields.depends('action', '_parent_action.complaint',
|
|
'_parent_action._parent_complaint.origin_id')
|
|
def on_change_with_complaint_origin_id(self, name=None):
|
|
if self.action and self.action.complaint:
|
|
return self.action.complaint.origin_id
|
|
|
|
|
|
class Action_SaleLine(_Action_Line, ModelView, ModelSQL):
|
|
'Customer Complaint Action - Sale Line'
|
|
__name__ = 'sale.complaint.action-sale.line'
|
|
|
|
line = fields.Many2One(
|
|
'sale.line', "Sale Line",
|
|
ondelete='RESTRICT', required=True,
|
|
domain=[
|
|
('type', '=', 'line'),
|
|
('sale', '=', Eval('complaint_origin_id', -1)),
|
|
])
|
|
|
|
@fields.depends('line')
|
|
def on_change_with_unit(self, name=None):
|
|
return self.line.unit if self.line else None
|
|
|
|
@fields.depends('quantity', 'line')
|
|
def get_quantity(self):
|
|
if self.quantity is not None:
|
|
return self.quantity
|
|
elif self.line:
|
|
return self.line.quantity
|
|
|
|
@fields.depends('unit_price', 'line')
|
|
def get_unit_price(self):
|
|
if self.unit_price is not None:
|
|
return self.unit_price
|
|
elif self.line:
|
|
return self.line.unit_price
|
|
|
|
|
|
class Action_InvoiceLine(_Action_Line, ModelView, ModelSQL):
|
|
'Customer Complaint Action - Invoice Line'
|
|
__name__ = 'sale.complaint.action-account.invoice.line'
|
|
|
|
line = fields.Many2One(
|
|
'account.invoice.line', 'Invoice Line',
|
|
ondelete='RESTRICT', required=True,
|
|
domain=[
|
|
('type', '=', 'line'),
|
|
('invoice', '=', Eval('complaint_origin_id', -1)),
|
|
])
|
|
|
|
@fields.depends('line')
|
|
def on_change_with_unit(self, name=None):
|
|
return self.line.unit if self.line else None
|
|
|
|
@fields.depends('quantity', 'line')
|
|
def get_quantity(self):
|
|
if self.quantity is not None:
|
|
return self.quantity
|
|
elif self.line:
|
|
return self.line.quantity
|
|
|
|
@fields.depends('unit_price', 'line')
|
|
def get_unit_price(self):
|
|
if self.unit_price is not None:
|
|
return self.unit_price
|
|
elif self.line:
|
|
return self.line.unit_price
|