Initial import from Docker volume
This commit is contained in:
787
modules/sale_complaint/complaint.py
Executable file
787
modules/sale_complaint/complaint.py
Executable file
@@ -0,0 +1,787 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user