first commit
This commit is contained in:
946
modules/sale_complaint/complaint.py
Normal file
946
modules/sale_complaint/complaint.py
Normal file
@@ -0,0 +1,946 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
import datetime as dt
|
||||
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 (
|
||||
ChatMixin, 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, PoolMeta
|
||||
from trytond.pyson import Bool, Eval, If
|
||||
from trytond.tools import grouped_slice
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
from .exceptions import ComplaintSimilarWarning
|
||||
|
||||
|
||||
class Type(DeactivableMixin, ModelSQL, ModelView):
|
||||
__name__ = 'sale.complaint.type'
|
||||
|
||||
name = fields.Char('Name', required=True)
|
||||
origin = fields.Many2One('ir.model', 'Origin', required=True,
|
||||
domain=[('name', 'in', [
|
||||
'sale.sale', 'sale.line',
|
||||
'account.invoice', 'account.invoice.line'])])
|
||||
|
||||
|
||||
class Complaint(Workflow, ModelSQL, ModelView, ChatMixin):
|
||||
__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', -1)),
|
||||
()),
|
||||
('company', '=', Eval('company', -1)),
|
||||
('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', -1)),
|
||||
()),
|
||||
('company', '=', Eval('company', -1)),
|
||||
('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().__setup__()
|
||||
t = cls.__table__()
|
||||
cls._sql_indexes.update({
|
||||
Index(t, (t.reference, Index.Similarity())),
|
||||
Index(
|
||||
t,
|
||||
(t.state, Index.Equality(cardinality='low')),
|
||||
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().__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.name, 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', '')),
|
||||
]
|
||||
|
||||
def chat_language(self, audience='internal'):
|
||||
language = super().chat_language(audience=audience)
|
||||
if audience == 'public':
|
||||
language = self.customer.lang.code if self.customer.lang else None
|
||||
return language
|
||||
|
||||
@classmethod
|
||||
def preprocess_values(cls, mode, values):
|
||||
pool = Pool()
|
||||
Configuration = pool.get('sale.configuration')
|
||||
values = super().preprocess_values(mode, values)
|
||||
if mode == 'create' and not values.get('number'):
|
||||
company_id = values.get('company', cls.default_company())
|
||||
if company_id is not None:
|
||||
configuration = Configuration(1)
|
||||
if sequence := configuration.get_multivalue(
|
||||
'complaint_sequence', company=company_id):
|
||||
values['number'] = sequence.get()
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
def check_modification(cls, mode, complaints, values=None, external=False):
|
||||
super().check_modification(
|
||||
mode, complaints, values=values, external=external)
|
||||
if mode == 'delete':
|
||||
for complaint in complaints:
|
||||
if complaint.state != 'draft':
|
||||
raise AccessError(gettext(
|
||||
'sale_complaint.msg_complaint_delete_draft',
|
||||
complaint=complaint.rec_name))
|
||||
|
||||
@classmethod
|
||||
def copy(cls, complaints, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
else:
|
||||
default = default.copy()
|
||||
default.setdefault('number', None)
|
||||
default.setdefault('reference')
|
||||
default.setdefault('submitted_by')
|
||||
default.setdefault('approved_by')
|
||||
default.setdefault('rejected_by')
|
||||
default.setdefault('cancelled_by')
|
||||
return super().copy(complaints, default=default)
|
||||
|
||||
@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):
|
||||
cls._check_similar(complaints)
|
||||
|
||||
@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()), []))
|
||||
|
||||
@classmethod
|
||||
def _check_similar(cls, complaints):
|
||||
pool = Pool()
|
||||
Warning = pool.get('res.user.warning')
|
||||
for sub_complaints in grouped_slice(complaints):
|
||||
sub_complaints = list(sub_complaints)
|
||||
domain = list(filter(None,
|
||||
(c._similar_domain() for c in sub_complaints)))
|
||||
if not domain:
|
||||
continue
|
||||
if cls.search(['OR'] + domain, order=[]):
|
||||
for complaint in sub_complaints:
|
||||
domain = complaint._similar_domain()
|
||||
if not domain:
|
||||
continue
|
||||
try:
|
||||
similar, = cls.search(domain, limit=1)
|
||||
except ValueError:
|
||||
continue
|
||||
warning_key = Warning.format(
|
||||
'complaint_similar', [complaint])
|
||||
if Warning.check(warning_key):
|
||||
raise ComplaintSimilarWarning(warning_key,
|
||||
gettext('sale_complaint.msg_complaint_similar',
|
||||
similar=similar.rec_name,
|
||||
complaint=complaint.rec_name))
|
||||
|
||||
def _similar_domain(self):
|
||||
pool = Pool()
|
||||
Sale = pool.get('sale.sale')
|
||||
SaleLine = pool.get('sale.line')
|
||||
Invoice = pool.get('account.invoice')
|
||||
InvoiceLine = pool.get('account.invoice.line')
|
||||
domain = ['OR',
|
||||
('origin', '=', str(self.origin)),
|
||||
]
|
||||
if isinstance(self.origin, Sale):
|
||||
domain.append(('origin.sale', '=', self.origin.id, 'sale.line'))
|
||||
elif isinstance(self.origin, SaleLine):
|
||||
domain.append(('origin', '=', str(self.origin.sale)))
|
||||
elif isinstance(self.origin, Invoice):
|
||||
domain.append(
|
||||
('origin.invoice', '=', self.origin.id,
|
||||
'account.invoice.line'))
|
||||
elif isinstance(self.origin, InvoiceLine):
|
||||
domain.append(('origin', '=', str(self.origin.invoice)))
|
||||
return [
|
||||
domain,
|
||||
('id', '!=', self.id),
|
||||
]
|
||||
|
||||
|
||||
class Action(ModelSQL, ModelView):
|
||||
__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')
|
||||
company = fields.Function(
|
||||
fields.Many2One('company.company', "Company"),
|
||||
'on_change_with_company')
|
||||
|
||||
@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
|
||||
elif (self.complaint.origin_model == 'sale.line'
|
||||
and self.complaint.origin.actual_quantity is not None):
|
||||
quantity = self.complaint.origin.actual_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:
|
||||
sale = self.complaint.origin
|
||||
amount = 0
|
||||
for line in sale.lines:
|
||||
if line.type != 'line':
|
||||
continue
|
||||
if line.actual_quantity is not None:
|
||||
quantity = line.actual_quantity
|
||||
else:
|
||||
quantity = line.quantity
|
||||
amount += sale.currency.round(
|
||||
Decimal(str(quantity)) * line.unit_price)
|
||||
return 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
|
||||
|
||||
@fields.depends('complaint', '_parent_complaint.company')
|
||||
def on_change_with_company(self, name=None):
|
||||
if self.complaint:
|
||||
return self.complaint.company
|
||||
|
||||
@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']
|
||||
default['quantity'] = lambda o: (
|
||||
o['actual_quantity']
|
||||
if o['actual_quantity'] is not None
|
||||
else o['quantity'])
|
||||
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
|
||||
else:
|
||||
default['quantity'] = (
|
||||
sale_line.actual_quantity
|
||||
if sale_line.actual_quantity is not None
|
||||
else sale_line.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 check_modification(cls, mode, actions, values=None, external=False):
|
||||
super().check_modification(
|
||||
mode, actions, values=values, external=external)
|
||||
if mode == 'delete':
|
||||
for action in actions:
|
||||
if action.result:
|
||||
raise AccessError(gettext(
|
||||
'sale_complaint.msg_action_delete_result',
|
||||
action=action.rec_name))
|
||||
|
||||
|
||||
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):
|
||||
__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:
|
||||
if self.line.actual_quantity is not None:
|
||||
return self.line.actual_quantity
|
||||
else:
|
||||
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):
|
||||
__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
|
||||
|
||||
|
||||
class Complaint_PromotionCoupon(metaclass=PoolMeta):
|
||||
__name__ = 'sale.complaint'
|
||||
|
||||
@classmethod
|
||||
def _actions_domains(cls):
|
||||
domains = super()._actions_domains()
|
||||
for name, domain in domains.items():
|
||||
domain.append('promotion_coupon')
|
||||
return domains
|
||||
|
||||
|
||||
class Action_PromotionCoupon(metaclass=PoolMeta):
|
||||
__name__ = 'sale.complaint.action'
|
||||
|
||||
_promtion_coupon_states = {
|
||||
'invisible': Eval('action') != 'promotion_coupon',
|
||||
'required': Eval('action') == 'promotion_coupon',
|
||||
}
|
||||
|
||||
promotion_coupon = fields.Many2One(
|
||||
'sale.promotion.coupon', "Promotion Coupon",
|
||||
domain=[
|
||||
('company', '=', Eval('company', -1)),
|
||||
('number_of_use', '=', 1),
|
||||
('per_party', '=', False),
|
||||
],
|
||||
states=_promtion_coupon_states)
|
||||
promotion_coupon_number = fields.Char(
|
||||
"Number", states=_promtion_coupon_states)
|
||||
promotion_coupon_duration = fields.TimeDelta(
|
||||
"Duration", states=_promtion_coupon_states)
|
||||
|
||||
del _promtion_coupon_states
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.action.selection.append(('promotion_coupon', "Promotion Coupon"))
|
||||
|
||||
@classmethod
|
||||
def default_promotion_coupon_duration(cls):
|
||||
return dt.timedelta(days=90)
|
||||
|
||||
@classmethod
|
||||
def _get_result(cls):
|
||||
return super()._get_result() + ['sale.promotion.coupon.number']
|
||||
|
||||
def do_promotion_coupon(self):
|
||||
pool = Pool()
|
||||
PromotionCouponNumber = pool.get('sale.promotion.coupon.number')
|
||||
Date = pool.get('ir.date')
|
||||
|
||||
with Transaction().set_context(company=self.company.id):
|
||||
today = Date.today()
|
||||
|
||||
return PromotionCouponNumber(
|
||||
number=self.promotion_coupon_number,
|
||||
coupon=self.promotion_coupon,
|
||||
company=self.company,
|
||||
start_date=today,
|
||||
end_date=today + self.promotion_coupon_duration,
|
||||
)
|
||||
Reference in New Issue
Block a user