Files
2026-03-14 09:42:12 +00:00

245 lines
8.4 KiB
Python

# 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 trytond.i18n import gettext
from trytond.model import ModelSQL, ValueMixin, fields
from trytond.model.exceptions import RequiredValidationError
from trytond.modules.product import round_price
from trytond.modules.purchase.purchase import (
get_shipments_returns, search_shipments_returns)
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction
purchase_drop_location = fields.Many2One(
'stock.location', "Purchase Drop Location", domain=[('type', '=', 'drop')])
class Request(metaclass=PoolMeta):
__name__ = 'purchase.request'
customer = fields.Many2One('party.party', 'Customer', readonly=True,
states={
'invisible': ~Eval('customer'),
},
context={
'company': Eval('company', -1),
},
depends={'company'})
delivery_address = fields.Many2One('party.address', 'Delivery Address',
domain=[('party', '=', Eval('customer', -1))],
states={
'invisible': ~Eval('customer'),
'readonly': Eval('state') != 'draft',
})
class Configuration(metaclass=PoolMeta):
__name__ = 'purchase.configuration'
purchase_drop_location = fields.MultiValue(purchase_drop_location)
@classmethod
def default_purchase_drop_location(cls, **pattern):
return cls.multivalue_model(
'purchase_drop_location').default_purchase_drop_location()
class ConfigurationPurchaseDropLocation(ModelSQL, ValueMixin):
__name__ = 'purchase.configuration.purchase_drop_location'
purchase_drop_location = purchase_drop_location
@classmethod
def default_purchase_drop_location(cls):
pool = Pool()
ModelData = pool.get('ir.model.data')
try:
return ModelData.get_id(
'sale_supply_drop_shipment', 'location_drop')
except KeyError:
return None
class Purchase(metaclass=PoolMeta):
__name__ = 'purchase.purchase'
customer = fields.Many2One('party.party', 'Customer', readonly=True,
states={
'invisible': ~Eval('customer'),
},
context={
'company': Eval('company', -1),
},
depends={'company'})
delivery_address = fields.Many2One('party.address', 'Delivery Address',
domain=[('party', '=', Eval('customer', -1))],
states={
'readonly': Eval('state') != 'draft',
'invisible': ~Eval('customer'),
})
drop_shipments = fields.Function(fields.Many2Many('stock.shipment.drop',
None, None, "Drop Shipments",
states={
'invisible': ~Eval('customer'),
}),
'get_drop_shipments', searcher='search_drop_shipments')
drop_location = fields.Many2One('stock.location', 'Drop Location',
domain=[('type', '=', 'drop')],
states={
'invisible': ~Eval('customer', False),
'required': Eval('customer', False),
})
@staticmethod
def default_drop_location():
pool = Pool()
PurchaseConfig = pool.get('purchase.configuration')
config = PurchaseConfig(1)
if config.purchase_drop_location:
return config.purchase_drop_location.id
@fields.depends('customer', 'delivery_address')
def on_change_customer(self):
self.delivery_address = None
if self.customer:
self.delivery_address = self.customer.address_get(type='delivery')
@property
def delivery_full_address(self):
address = super().delivery_full_address
if self.customer and self.delivery_address:
address = self.delivery_address.full_address
return address
get_drop_shipments = get_shipments_returns('stock.shipment.drop')
search_drop_shipments = search_shipments_returns('stock.shipment.drop')
def check_for_quotation(self):
super().check_for_quotation()
if self.customer and not self.delivery_address:
raise RequiredValidationError(
gettext('sale_supply_drop_shipment'
'.msg_delivery_address_required_quotation_purchase') % {
'purchase': self.rec_name,
})
@classmethod
def _process_shipment(cls, purchases):
pool = Pool()
DropShipment = pool.get('stock.shipment.drop')
drop_shipments = []
for purchase in purchases:
if purchase.customer:
moves = purchase.create_move('in')
if moves:
drop_shipment = purchase.create_drop_shipment()
drop_shipment.supplier_moves = moves
drop_shipments.append(drop_shipment)
DropShipment.save(drop_shipments)
DropShipment.wait(drop_shipments)
super()._process_shipment(purchases)
def create_drop_shipment(self):
pool = Pool()
DropShipment = pool.get('stock.shipment.drop')
return DropShipment(
company=self.company,
supplier=self.party,
contact_address=self.party.address_get(),
customer=self.customer,
delivery_address=self.delivery_address,
)
class Line(metaclass=PoolMeta):
__name__ = 'purchase.line'
def get_to_location(self, name):
result = super().get_to_location(name)
if self.purchase.customer:
return self.purchase.drop_location.id
return result
def get_move(self, move_type):
pool = Pool()
Currency = pool.get('currency.currency')
move = super().get_move(move_type)
if move and self.purchase.customer:
cost_price = Currency.compute(
move.currency, move.unit_price, move.company.currency)
move.cost_price = round_price(cost_price)
return move
class ProductSupplier(metaclass=PoolMeta):
__name__ = 'purchase.product_supplier'
drop_shipment = fields.Boolean('Drop Shipment',
states={
'invisible': ~Eval('drop_shipment_available', False),
})
drop_shipment_available = fields.Function(
fields.Boolean("Drop Shipment Available"),
'on_change_with_drop_shipment_available')
@fields.depends('product', 'template',
'_parent_product.type', '_parent_product.supply_on_sale',
'_parent_template.type', '_parent_template.supply_on_sale')
def on_change_with_drop_shipment_available(self, name=None):
product = self.product or self.template
if product and product.type in {'goods', 'assets'}:
return bool(product.supply_on_sale)
class RequestCreatePurchase(metaclass=PoolMeta):
__name__ = 'purchase.request.create_purchase'
@classmethod
def _group_purchase_key(cls, requests, request):
result = super()._group_purchase_key(requests, request)
result += (
('customer', request.customer.id if request.customer else None),
('delivery_address', request.delivery_address.id
if request.delivery_address else None),
)
return result
class HandleShipmentException(metaclass=PoolMeta):
__name__ = 'purchase.handle.shipment.exception'
def transition_handle(self):
pool = Pool()
Sale = pool.get('sale.sale')
Move = pool.get('stock.move')
transaction = Transaction()
context = transaction.context
super().transition_handle()
sales = set()
moves = set()
to_recreate = set(self.ask.recreate_moves)
domain_moves = set(self.ask.domain_moves)
for line in self.record.lines:
if not set(line.moves) & domain_moves:
continue
if not any(m in to_recreate for m in line.moves):
for request in line.requests:
for sale_line in request.sale_lines:
moves.update({m for m in sale_line.moves
if (m.state != 'done'
and m.from_location.type == 'drop')})
sales.add(sale_line.sale)
if moves:
Move.cancel(moves)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
return 'end'