first commit
This commit is contained in:
700
modules/purchase_request_quotation/purchase.py
Normal file
700
modules/purchase_request_quotation/purchase.py
Normal file
@@ -0,0 +1,700 @@
|
||||
# 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
|
||||
from functools import wraps
|
||||
from itertools import groupby
|
||||
|
||||
from sql import Null
|
||||
from sql.conditionals import Case
|
||||
from sql.functions import CharLength
|
||||
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import (
|
||||
ChatMixin, Index, ModelSQL, ModelView, Workflow, fields)
|
||||
from trytond.modules.company import CompanyReport
|
||||
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, Id, If
|
||||
from trytond.tools import sortable_values
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import (
|
||||
Button, StateAction, StateTransition, StateView, Wizard)
|
||||
|
||||
from .exceptions import PreviousQuotation
|
||||
|
||||
|
||||
def process_request(func):
|
||||
@wraps(func)
|
||||
def wrapper(cls, quotations):
|
||||
pool = Pool()
|
||||
Request = pool.get('purchase.request')
|
||||
result = func(cls, quotations)
|
||||
requests = [l.request for q in quotations for l in q.lines]
|
||||
Request.update_state(requests)
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
|
||||
class Configuration(metaclass=PoolMeta):
|
||||
__name__ = 'purchase.configuration'
|
||||
purchase_request_quotation_sequence = fields.MultiValue(fields.Many2One(
|
||||
'ir.sequence', 'Purchase Request Quotation Sequence',
|
||||
required=True,
|
||||
domain=[
|
||||
('company', 'in',
|
||||
[Eval('context', {}).get('company', -1), None]),
|
||||
('sequence_type', '=',
|
||||
Id('purchase_request_quotation',
|
||||
'sequence_type_purchase_request_quotation')),
|
||||
]))
|
||||
|
||||
@classmethod
|
||||
def multivalue_model(cls, field):
|
||||
pool = Pool()
|
||||
if field == 'purchase_request_quotation_sequence':
|
||||
return pool.get('purchase.configuration.sequence')
|
||||
return super().multivalue_model(field)
|
||||
|
||||
@classmethod
|
||||
def default_purchase_request_quotation_sequence(cls, **pattern):
|
||||
return cls.multivalue_model('purchase_request_quotation_sequence'
|
||||
).default_purchase_request_quotation_sequence()
|
||||
|
||||
|
||||
class ConfigurationSequence(metaclass=PoolMeta):
|
||||
__name__ = 'purchase.configuration.sequence'
|
||||
purchase_request_quotation_sequence = fields.Many2One(
|
||||
'ir.sequence', 'Purchase Request Quotation Sequence',
|
||||
required=True,
|
||||
domain=[
|
||||
('company', 'in',
|
||||
[Eval('context', {}).get('company', -1), None]),
|
||||
('sequence_type', '=',
|
||||
Id('purchase_request_quotation',
|
||||
'sequence_type_purchase_request_quotation')),
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def default_purchase_request_quotation_sequence(cls):
|
||||
pool = Pool()
|
||||
ModelData = pool.get('ir.model.data')
|
||||
try:
|
||||
return ModelData.get_id(
|
||||
'purchase_request_quotation',
|
||||
'sequence_purchase_request_quotation')
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
class Quotation(Workflow, ModelSQL, ModelView, ChatMixin):
|
||||
__name__ = 'purchase.request.quotation'
|
||||
_rec_name = 'number'
|
||||
|
||||
number = fields.Char('Number', readonly=True,
|
||||
states={
|
||||
'required': ~Eval('state').in_(['draft', 'cancelled'])
|
||||
},
|
||||
help="The unique identifier of the quotation.")
|
||||
revision = fields.Integer('Revision', readonly=True,
|
||||
help="Number incremented each time the quotation is sent.")
|
||||
reference = fields.Char(
|
||||
"Reference",
|
||||
help="The reference used by the supplier.")
|
||||
company = fields.Many2One(
|
||||
'company.company', "Company", required=True,
|
||||
states={
|
||||
'readonly': Eval('state') != 'draft',
|
||||
})
|
||||
warehouse = fields.Many2One('stock.location', 'Warehouse',
|
||||
domain=[('type', '=', 'warehouse')])
|
||||
supplier = fields.Many2One(
|
||||
'party.party', "Supplier", required=True,
|
||||
states={
|
||||
'readonly': Eval('lines', [0]) & Eval('supplier'),
|
||||
},
|
||||
context={
|
||||
'company': Eval('company', -1),
|
||||
},
|
||||
depends={'company'})
|
||||
supplier_address = fields.Many2One('party.address', 'Supplier Address',
|
||||
domain=[
|
||||
('party', '=', Eval('supplier', -1)),
|
||||
])
|
||||
lines = fields.One2Many('purchase.request.quotation.line', 'quotation',
|
||||
'Lines', states={
|
||||
'readonly': Eval('state') != 'draft',
|
||||
})
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('sent', 'Sent'),
|
||||
('received', 'Received'),
|
||||
('rejected', 'Rejected'),
|
||||
('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', 'sent'])),
|
||||
})
|
||||
cls._transitions |= set((
|
||||
('draft', 'cancelled'),
|
||||
('cancelled', 'draft'),
|
||||
('draft', 'sent'),
|
||||
('sent', 'rejected'),
|
||||
('sent', 'received'),
|
||||
('sent', 'draft'),
|
||||
('received', 'rejected'),
|
||||
('rejected', 'received'),
|
||||
))
|
||||
cls._buttons.update({
|
||||
'cancel': {
|
||||
'invisible': Eval('state') != 'draft',
|
||||
},
|
||||
'draft': {
|
||||
'invisible': ~Eval('state').in_(['cancelled', 'sent']),
|
||||
'icon': If(Eval('state') == 'cancelled',
|
||||
'tryton-undo',
|
||||
'tryton-back'),
|
||||
},
|
||||
'send': {
|
||||
'invisible': ((Eval('state') != 'draft')
|
||||
| ~Eval('lines', [])),
|
||||
'readonly': ~Eval('lines', []),
|
||||
},
|
||||
'receive': {
|
||||
'invisible': ~Eval('state').in_(['sent', 'rejected']),
|
||||
},
|
||||
'reject': {
|
||||
'invisible': ~Eval('state').in_(['sent', 'received']),
|
||||
},
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def order_number(cls, tables):
|
||||
table, _ = tables[None]
|
||||
return [
|
||||
~((table.state == 'cancelled') & (table.number == Null)),
|
||||
CharLength(table.number), table.number]
|
||||
|
||||
@classmethod
|
||||
def default_company(cls):
|
||||
return Transaction().context.get('company')
|
||||
|
||||
@classmethod
|
||||
def default_state(cls):
|
||||
return 'draft'
|
||||
|
||||
@classmethod
|
||||
def default_revision(cls):
|
||||
return 1
|
||||
|
||||
@classmethod
|
||||
def default_warehouse(cls):
|
||||
Location = Pool().get('stock.location')
|
||||
return Location.get_default_warehouse()
|
||||
|
||||
@classmethod
|
||||
def set_number(cls, quotations):
|
||||
pool = Pool()
|
||||
Config = pool.get('purchase.configuration')
|
||||
|
||||
config = Config(1)
|
||||
for company, c_quotations in groupby(
|
||||
quotations, key=lambda q: q.company):
|
||||
missing_number = []
|
||||
for quotation in c_quotations:
|
||||
if quotation.number:
|
||||
quotation.revision += 1
|
||||
else:
|
||||
missing_number.append(quotation)
|
||||
if missing_number:
|
||||
sequence = config.get_multivalue(
|
||||
'purchase_request_quotation_sequence', company=company.id)
|
||||
for quotation, number in zip(
|
||||
missing_number,
|
||||
sequence.get_many(len(missing_number))):
|
||||
quotation.number = number
|
||||
cls.save(quotations)
|
||||
|
||||
@fields.depends('supplier')
|
||||
def on_change_supplier(self):
|
||||
self.supplier_address = None
|
||||
if self.supplier:
|
||||
self.supplier_address = self.supplier.address_get()
|
||||
|
||||
def chat_language(self, audience='internal'):
|
||||
language = super().chat_language(audience=audience)
|
||||
if audience == 'public':
|
||||
language = self.supplier.lang.code if self.supplier.lang else None
|
||||
return language
|
||||
|
||||
@classmethod
|
||||
def copy(cls, groups, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
else:
|
||||
default = default.copy()
|
||||
default.setdefault('number', None)
|
||||
default.setdefault('reference')
|
||||
default.setdefault('revision', cls.default_revision())
|
||||
return super().copy(groups, default=default)
|
||||
|
||||
@property
|
||||
def delivery_full_address(self):
|
||||
if self.warehouse and self.warehouse.address:
|
||||
return self.warehouse.address.full_address
|
||||
return ''
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('cancelled')
|
||||
def cancel(cls, quotations):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('draft')
|
||||
def draft(cls, quotations):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('sent')
|
||||
def send(cls, quotations):
|
||||
cls.set_number(quotations)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@process_request
|
||||
@Workflow.transition('received')
|
||||
def receive(cls, quotations):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@process_request
|
||||
@Workflow.transition('rejected')
|
||||
def reject(cls, quotations):
|
||||
pass
|
||||
|
||||
|
||||
class QuotationLine(ModelSQL, ModelView):
|
||||
__name__ = 'purchase.request.quotation.line'
|
||||
|
||||
supplier = fields.Function(fields.Many2One('party.party', 'Supplier'),
|
||||
'get_supplier')
|
||||
supply_date = fields.Date('Supply Date',
|
||||
help="When it should be delivered.")
|
||||
product = fields.Function(fields.Many2One('product.product', 'Product'),
|
||||
'get_product', searcher='search_product')
|
||||
description = fields.Text('Description',
|
||||
states={
|
||||
'required': ~Eval('product')
|
||||
})
|
||||
quantity = fields.Float("Quantity", digits='unit', required=True)
|
||||
unit = fields.Many2One(
|
||||
'product.uom', 'Unit', ondelete='RESTRICT',
|
||||
states={
|
||||
'required': Bool(Eval('product')),
|
||||
},
|
||||
domain=[
|
||||
If(Bool(Eval('product_uom_category')),
|
||||
('category', '=', Eval('product_uom_category')),
|
||||
('category', '!=', -1)),
|
||||
])
|
||||
product_uom_category = fields.Function(
|
||||
fields.Many2One(
|
||||
'product.uom.category', "Product UoM Category",
|
||||
help="The category of Unit of Measure for the product."),
|
||||
'on_change_with_product_uom_category')
|
||||
unit_price = Monetary(
|
||||
"Unit Price", currency='currency', digits=price_digits)
|
||||
currency = fields.Many2One('currency.currency', 'Currency',
|
||||
states={
|
||||
'required': Bool(Eval('unit_price')),
|
||||
})
|
||||
request = fields.Many2One(
|
||||
'purchase.request', "Request", ondelete='CASCADE', required=True,
|
||||
domain=[
|
||||
If(Eval('quotation_state') == 'draft',
|
||||
('state', 'in', ['draft', 'quotation', 'received']), (), ),
|
||||
],
|
||||
states={
|
||||
'readonly': Eval('quotation_state') != 'draft'
|
||||
},
|
||||
help="The request which this line belongs to.")
|
||||
quotation = fields.Many2One('purchase.request.quotation', 'Quotation',
|
||||
ondelete='CASCADE', required=True,
|
||||
domain=[
|
||||
('supplier', '=', Eval('supplier', -1)),
|
||||
])
|
||||
quotation_state = fields.Function(fields.Selection(
|
||||
'get_quotation_state', 'Quotation State'),
|
||||
'on_change_with_quotation_state', searcher='search_quotation_state')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.__access__.add('quotation')
|
||||
|
||||
@staticmethod
|
||||
def order_quotation_state(tables):
|
||||
pool = Pool()
|
||||
Quotation = pool.get('purchase.request.quotation')
|
||||
table, _ = tables[None]
|
||||
if 'quotation' not in tables:
|
||||
quotation = Quotation.__table__()
|
||||
tables['quotation'] = {
|
||||
None: (quotation, table.quotation == quotation.id),
|
||||
}
|
||||
else:
|
||||
quotation, _ = tables['quotation'][None]
|
||||
return [Case((quotation.state == 'received', 0), else_=1),
|
||||
quotation.state]
|
||||
|
||||
def get_supplier(self, name):
|
||||
if self.quotation and self.quotation.supplier:
|
||||
return self.quotation.supplier.id
|
||||
|
||||
@fields.depends('request',
|
||||
'_parent_request.product', '_parent_request.description',
|
||||
'_parent_request.quantity', '_parent_request.unit',
|
||||
'_parent_request.company', '_parent_request.supply_date')
|
||||
def on_change_request(self):
|
||||
if self.request:
|
||||
self.product = self.request.product
|
||||
self.description = self.request.description
|
||||
self.quantity = self.request.quantity
|
||||
self.unit = self.request.unit
|
||||
if self.request.company:
|
||||
self.currency = self.request.company.currency
|
||||
self.supply_date = self.request.supply_date or datetime.date.max
|
||||
|
||||
@fields.depends('product')
|
||||
def on_change_with_product_uom_category(self, name=None):
|
||||
return self.product.default_uom_category if self.product else None
|
||||
|
||||
@classmethod
|
||||
def get_quotation_state(cls):
|
||||
pool = Pool()
|
||||
Quotation = pool.get('purchase.request.quotation')
|
||||
return (Quotation.fields_get(
|
||||
['state'])['state']['selection'])
|
||||
|
||||
@fields.depends('quotation', '_parent_quotation.state')
|
||||
def on_change_with_quotation_state(self, name=None):
|
||||
pool = Pool()
|
||||
Quotation = pool.get('purchase.request.quotation')
|
||||
if self.quotation:
|
||||
return self.quotation.state
|
||||
return Quotation.default_state()
|
||||
|
||||
@classmethod
|
||||
def search_quotation_state(cls, name, clause):
|
||||
return [('quotation.state',) + tuple(clause[1:])]
|
||||
|
||||
def get_rec_name(self, name):
|
||||
return '%s - %s' % (self.quotation.rec_name, self.supplier.rec_name)
|
||||
|
||||
@classmethod
|
||||
def search_rec_name(cls, name, clause):
|
||||
domain = []
|
||||
_, operator, value = clause
|
||||
if value is not None:
|
||||
names = clause[2].split(' - ', 1)
|
||||
domain.append(('quotation', operator, names[0]))
|
||||
if len(names) != 1 and names[1]:
|
||||
domain.append(('supplier', operator, names[1]))
|
||||
if operator.startswith('!') or operator.startswith('not'):
|
||||
domain.insert(0, 'OR')
|
||||
elif not operator.startswith('!') and not operator.startswith('not'):
|
||||
domain.append(('id', '<', 0))
|
||||
return domain
|
||||
|
||||
@classmethod
|
||||
def on_delete(cls, lines):
|
||||
pool = Pool()
|
||||
Request = pool.get('purchase.request')
|
||||
callback = super().on_delete(lines)
|
||||
requests = {l.request for l in lines}
|
||||
if requests:
|
||||
requests = Request.browse(requests)
|
||||
callback.append(lambda: Request.update_state(requests))
|
||||
return callback
|
||||
|
||||
def get_product(self, name):
|
||||
if self.request and self.request.product:
|
||||
return self.request.product.id
|
||||
|
||||
@classmethod
|
||||
def search_product(cls, name, clause):
|
||||
return [('request.' + clause[0],) + tuple(clause[1:])]
|
||||
|
||||
|
||||
class PurchaseRequestQuotationReport(CompanyReport):
|
||||
__name__ = 'purchase.request.quotation'
|
||||
|
||||
@classmethod
|
||||
def execute(cls, ids, data):
|
||||
with Transaction().set_context(address_with_party=True):
|
||||
return super(
|
||||
PurchaseRequestQuotationReport, cls).execute(ids, data)
|
||||
|
||||
@classmethod
|
||||
def get_context(cls, records, header, data):
|
||||
pool = Pool()
|
||||
Date = pool.get('ir.date')
|
||||
context = super().get_context(records, header, data)
|
||||
company = header.get('company')
|
||||
with Transaction().set_context(
|
||||
company=company.id if company else None):
|
||||
context['today'] = Date.today()
|
||||
return context
|
||||
|
||||
|
||||
class CreatePurchaseRequestQuotationAskSuppliers(ModelView):
|
||||
__name__ = 'purchase.request.quotation.create.ask_suppliers'
|
||||
|
||||
suppliers = fields.Many2Many('party.party', None, None, 'Suppliers',
|
||||
required=True)
|
||||
|
||||
|
||||
class CreatePurchaseRequestQuotationSucceed(ModelView):
|
||||
__name__ = 'purchase.request.quotation.create.succeed'
|
||||
|
||||
number_quotations = fields.Integer('Number of Created Quotations',
|
||||
readonly=True)
|
||||
|
||||
|
||||
class CreatePurchaseRequestQuotation(Wizard):
|
||||
__name__ = 'purchase.request.quotation.create'
|
||||
|
||||
start = StateTransition()
|
||||
ask_suppliers = StateView(
|
||||
'purchase.request.quotation.create.ask_suppliers',
|
||||
'purchase_request_quotation.'
|
||||
'purchase_request_quotation_create_ask_suppliers', [
|
||||
Button('Cancel', 'end', 'tryton-cancel'),
|
||||
Button('Process', 'create_quotations', 'tryton-ok', default=True),
|
||||
])
|
||||
create_quotations = StateAction(
|
||||
'purchase_request_quotation.act_purchase_request_quotation_form')
|
||||
|
||||
def transition_start(self):
|
||||
pool = Pool()
|
||||
Warning = pool.get('res.user.warning')
|
||||
|
||||
reqs = [
|
||||
r for r in self.records
|
||||
if r.state in {'draft', 'quotation', 'received'}]
|
||||
if reqs:
|
||||
reqs = [r for r in reqs if r.state in {'quotation', 'received'}]
|
||||
if reqs:
|
||||
warning_key = Warning.format(
|
||||
'purchase_request_quotation_create', reqs)
|
||||
if Warning.check(warning_key):
|
||||
names = ', '.join(r.rec_name for r in reqs[:5])
|
||||
if len(reqs) > 5:
|
||||
names += '...'
|
||||
raise PreviousQuotation(
|
||||
warning_key,
|
||||
gettext('purchase_request_quotation'
|
||||
'.msg_previous_quotation',
|
||||
requests=names))
|
||||
return 'ask_suppliers'
|
||||
return 'end'
|
||||
|
||||
def default_ask_suppliers(self, fields):
|
||||
reqs = [
|
||||
r for r in self.records
|
||||
if r.party and r.state in {'draft', 'quotation', 'received'}]
|
||||
return {
|
||||
'suppliers': [r.party.id for r in reqs],
|
||||
}
|
||||
|
||||
def default_succeed(self, fields):
|
||||
return {
|
||||
'number_quotations': self.succeed.number_quotations,
|
||||
}
|
||||
|
||||
def filter_request(self, request, supplier):
|
||||
return request
|
||||
|
||||
def _group_request_key(self, request):
|
||||
return (('company', request.company),)
|
||||
|
||||
def do_create_quotations(self, action):
|
||||
pool = Pool()
|
||||
Quotation = pool.get('purchase.request.quotation')
|
||||
QuotationLine = pool.get('purchase.request.quotation.line')
|
||||
quotations = []
|
||||
lines = []
|
||||
|
||||
requests = [
|
||||
r for r in self.records
|
||||
if r.state in {'draft', 'quotation', 'received'}]
|
||||
for supplier in self.ask_suppliers.suppliers:
|
||||
sub_requests = [
|
||||
r for r in requests if self.filter_request(r, supplier)]
|
||||
sub_requests = sorted(
|
||||
sub_requests, key=sortable_values(self._group_request_key))
|
||||
for key, grouped_requests in groupby(
|
||||
sub_requests, key=self._group_request_key):
|
||||
quotation = self.get_quotation(supplier, key)
|
||||
for request in grouped_requests:
|
||||
line = self.get_quotation_line(request, quotation)
|
||||
line.quotation = quotation
|
||||
lines.append(line)
|
||||
quotations.append(quotation)
|
||||
Quotation.save(quotations)
|
||||
QuotationLine.save(lines)
|
||||
|
||||
self.model.update_state(requests)
|
||||
action['domain'] = []
|
||||
if len(quotations) == 1:
|
||||
action['views'].reverse()
|
||||
return action, {
|
||||
'res_id': list(map(int, quotations))
|
||||
}
|
||||
|
||||
def get_quotation(self, supplier, key):
|
||||
pool = Pool()
|
||||
Quotation = pool.get('purchase.request.quotation')
|
||||
quotation = Quotation()
|
||||
quotation.supplier = supplier
|
||||
quotation.supplier_address = supplier.address_get()
|
||||
for f, v in key:
|
||||
setattr(quotation, f, v)
|
||||
return quotation
|
||||
|
||||
def get_quotation_line(self, request, quotation):
|
||||
pool = Pool()
|
||||
QuotationLine = pool.get('purchase.request.quotation.line')
|
||||
quotation_line = QuotationLine()
|
||||
quotation_line.request = request
|
||||
quotation_line.description = request.description
|
||||
quotation_line.quantity = request.quantity
|
||||
quotation_line.unit = request.unit
|
||||
quotation_line.currency = request.currency
|
||||
quotation_line.supply_date = request.supply_date or datetime.date.max
|
||||
return quotation_line
|
||||
|
||||
|
||||
class PurchaseRequest(metaclass=PoolMeta):
|
||||
__name__ = 'purchase.request'
|
||||
|
||||
quotation_lines = fields.One2Many(
|
||||
'purchase.request.quotation.line', 'request', 'Quotation Lines',
|
||||
)
|
||||
quotation_lines_active = fields.One2Many(
|
||||
'purchase.request.quotation.line', 'request', 'Active Quotation Lines',
|
||||
filter=[('quotation.state', 'in', ['draft', 'sent', 'received'])],
|
||||
order=[('quotation_state', 'ASC'), ('unit_price', 'ASC')])
|
||||
best_quotation_line = fields.Function(fields.Many2One(
|
||||
'purchase.request.quotation.line', 'Best Quotation Line'),
|
||||
'get_best_quotation')
|
||||
preferred_quotation_line = fields.Many2One(
|
||||
'purchase.request.quotation.line', 'Preferred Quotation Line',
|
||||
domain=[
|
||||
('quotation_state', '=', 'received'),
|
||||
('request', '=', Eval('id', -1))
|
||||
],
|
||||
help="The quotation that will be chosen to create the purchase\n"
|
||||
"otherwise first ordered received quotation line will be selected.")
|
||||
|
||||
@property
|
||||
def currency(self):
|
||||
currency = super().currency
|
||||
if self.best_quotation_line:
|
||||
return self.best_quotation_line.currency
|
||||
return currency
|
||||
|
||||
def get_best_quotation(self, name):
|
||||
if self.preferred_quotation_line:
|
||||
return self.preferred_quotation_line
|
||||
else:
|
||||
for line in self.quotation_lines_active:
|
||||
if line.quotation_state == 'received':
|
||||
return line
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
selection = [('quotation', 'Quotation'), ('received', 'Received')]
|
||||
for s in selection:
|
||||
if s not in cls.state.selection:
|
||||
cls.state.selection.append(s)
|
||||
cls._buttons.update({
|
||||
'create_quotation': {
|
||||
'invisible': ~Eval('state').in_(
|
||||
['draft', 'quotation', 'received']),
|
||||
'depends': ['state'],
|
||||
},
|
||||
})
|
||||
|
||||
def get_state(self):
|
||||
state = super().get_state()
|
||||
if state == 'draft' and self.quotation_lines:
|
||||
state = 'quotation'
|
||||
if any(l.quotation_state == 'received'
|
||||
for l in self.quotation_lines):
|
||||
state = 'received'
|
||||
return state
|
||||
|
||||
@classmethod
|
||||
@ModelView.button_action(
|
||||
'purchase_request_quotation.wizard_create_quotation')
|
||||
def create_quotation(cls, requests):
|
||||
pass
|
||||
|
||||
|
||||
class CreatePurchase(Wizard):
|
||||
__name__ = 'purchase.request.create_purchase'
|
||||
|
||||
init = StateTransition()
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
|
||||
def transition_start(self):
|
||||
to_save = []
|
||||
reqs = [r for r in self.records
|
||||
if not r.purchase_line and r.quotation_lines]
|
||||
to_save = []
|
||||
for req in reqs:
|
||||
if req.best_quotation_line:
|
||||
to_save.append(self.apply_quotation(req))
|
||||
if to_save:
|
||||
self.model.save(to_save)
|
||||
state = super().transition_start()
|
||||
return state
|
||||
|
||||
def apply_quotation(self, request):
|
||||
request.party = request.best_quotation_line.supplier.id
|
||||
request.description = request.best_quotation_line.description
|
||||
request.quantity = request.best_quotation_line.quantity
|
||||
if not request.preferred_quotation_line:
|
||||
request.preferred_quotation_line = request.best_quotation_line
|
||||
return request
|
||||
|
||||
@classmethod
|
||||
def compute_purchase_line(cls, key, requests, purchase):
|
||||
line = super().compute_purchase_line(key,
|
||||
requests, purchase)
|
||||
try:
|
||||
line.unit_price = min(req.best_quotation_line.unit_price
|
||||
for req in requests if req.best_quotation_line)
|
||||
except ValueError:
|
||||
pass
|
||||
return line
|
||||
Reference in New Issue
Block a user