Fee linked curr
This commit is contained in:
@@ -59,11 +59,11 @@ class Fee(ModelSQL,ModelView):
|
||||
], "P/R", required=True)
|
||||
product = fields.Many2One('product.product',"Product", required=True, domain=[('type', '=', 'service')])
|
||||
price = fields.Numeric("Price",digits=(1,4))
|
||||
enable_linked_currency = fields.Boolean("Linked currencies")
|
||||
enable_linked_currency = fields.Boolean("Linked curr")
|
||||
linked_price = fields.Numeric("Linked Price", digits='unit', states={
|
||||
'invisible': ~Eval('enable_linked_currency'),
|
||||
}, depends=['enable_linked_currency'])
|
||||
linked_currency = fields.Many2One('currency.linked', "Linked Currency",
|
||||
linked_currency = fields.Many2One('currency.linked', "Linked curr",
|
||||
states={
|
||||
'invisible': ~Eval('enable_linked_currency'),
|
||||
}, depends=['enable_linked_currency'])
|
||||
@@ -366,18 +366,26 @@ class Fee(ModelSQL,ModelView):
|
||||
if fl[0].lot.sale_line:
|
||||
return fl[0].lot.sale_line.unit
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@filter_state('not invoiced')
|
||||
def invoice(cls, fees):
|
||||
Purchase = Pool().get('purchase.purchase')
|
||||
FeeLots = Pool().get('fee.lots')
|
||||
for fee in fees:
|
||||
if fee.purchase:
|
||||
fl = FeeLots.search([('fee','=',fee.id)])
|
||||
logger.info("PROCESS_FROM_FEE:%s",fl)
|
||||
Purchase._process_invoice([fee.purchase],[e.lot for e in fl],'service')
|
||||
cls.write(fees, {'state': 'invoiced',})
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@filter_state('not invoiced')
|
||||
def invoice(cls, fees):
|
||||
Purchase = Pool().get('purchase.purchase')
|
||||
FeeLots = Pool().get('fee.lots')
|
||||
fees_to_invoice = []
|
||||
for fee in fees:
|
||||
fee.ensure_ordered_purchase()
|
||||
if fee.purchase:
|
||||
fl = FeeLots.search([('fee','=',fee.id)])
|
||||
logger.info("PROCESS_FROM_FEE:%s",fl)
|
||||
Purchase._process_invoice([fee.purchase],[e.lot for e in fl],'service')
|
||||
fees_to_invoice.append(fee)
|
||||
else:
|
||||
raise UserError(
|
||||
"Cannot invoice fee %s: no generated service purchase "
|
||||
"is linked to this fee." % getattr(fee, 'id', None))
|
||||
if fees_to_invoice:
|
||||
cls.write(fees_to_invoice, {'state': 'invoiced',})
|
||||
|
||||
@classmethod
|
||||
def default_type(cls):
|
||||
@@ -810,17 +818,117 @@ class Fee(ModelSQL,ModelView):
|
||||
"accounting period is missing or incomplete. Do you want to "
|
||||
"continue without creating the accrual entry?")
|
||||
|
||||
def _can_create_ordered_purchase(self):
|
||||
return (
|
||||
getattr(self, 'type', None) == 'ordered'
|
||||
and not getattr(self, 'purchase', None)
|
||||
and getattr(self, 'currency', None)
|
||||
and getattr(self, 'price', None) is not None)
|
||||
|
||||
def _get_ordered_purchase_quantity_unit(self):
|
||||
quantity = Decimal(0)
|
||||
unit = None
|
||||
lots = self._get_effective_fee_lots()
|
||||
if lots:
|
||||
quantity = sum(
|
||||
Decimal(lot.get_current_quantity_converted() or 0)
|
||||
for lot in lots)
|
||||
first_lot = lots[0]
|
||||
if getattr(first_lot, 'line', None):
|
||||
unit = first_lot.line.unit
|
||||
elif getattr(first_lot, 'sale_line', None):
|
||||
unit = first_lot.sale_line.unit
|
||||
elif getattr(self, 'quantity', None) is not None:
|
||||
quantity = Decimal(self.quantity or 0)
|
||||
unit = getattr(self, 'unit', None)
|
||||
return quantity, unit
|
||||
|
||||
def _get_ordered_purchase_locations(self):
|
||||
if getattr(self, 'shipment_in', None):
|
||||
return self.shipment_in.from_location, self.shipment_in.to_location
|
||||
if getattr(self, 'line', None):
|
||||
return self.line.purchase.from_location, self.line.purchase.to_location
|
||||
return self.sale_line.sale.from_location, self.sale_line.sale.to_location
|
||||
|
||||
def _get_ordered_purchase_payment_term(self):
|
||||
if getattr(self, 'shipment_in', None) and self.shipment_in.lotqt:
|
||||
return self.shipment_in.lotqt[0].lot_p.line.purchase.payment_term
|
||||
if getattr(self, 'line', None):
|
||||
return self.line.purchase.payment_term
|
||||
return self.sale_line.sale.payment_term
|
||||
|
||||
def _set_ordered_purchase_line_values(self, line, quantity, unit):
|
||||
line.product = self.product
|
||||
line.quantity = round(quantity, 5)
|
||||
line.unit = unit
|
||||
line.fee_ = self.id
|
||||
if getattr(self, 'price', None) is not None:
|
||||
fee_price = self.get_price_per_qt()
|
||||
logger.info("GET_FEE_PRICE_PER_QT:%s", fee_price)
|
||||
line.unit_price = round(Decimal(fee_price), 4)
|
||||
if self.mode == 'lumpsum':
|
||||
line.quantity = 1
|
||||
line.unit_price = round(Decimal(self.amount or 0), 4)
|
||||
elif self.mode == 'ppack':
|
||||
line.unit_price = self.price
|
||||
|
||||
def _create_fee_accruals(self):
|
||||
if self.sale_line:
|
||||
return
|
||||
FeeLots = Pool().get('fee.lots')
|
||||
feelots = FeeLots.search(['fee','=',self.id])
|
||||
for fl in feelots:
|
||||
if self.product.template.landed_cost:
|
||||
move = fl.lot.get_received_move()
|
||||
if move:
|
||||
Warning = Pool().get('res.user.warning')
|
||||
warning_name = Warning.format("Lot ever received", [])
|
||||
if Warning.check(warning_name):
|
||||
raise UserWarning(warning_name,
|
||||
"By clicking yes, an accrual for this fee will be created")
|
||||
account_move = move._get_account_stock_move_fee(self)
|
||||
self.__class__._save_fee_accrual_or_warn(
|
||||
account_move, self, fl.lot)
|
||||
else:
|
||||
account_move = self._get_account_move_fee(fl.lot)
|
||||
self.__class__._save_fee_accrual_or_warn(
|
||||
account_move, self, fl.lot)
|
||||
|
||||
def ensure_ordered_purchase(self):
|
||||
if not self._can_create_ordered_purchase():
|
||||
return False
|
||||
Purchase = Pool().get('purchase.purchase')
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
quantity, unit = self._get_ordered_purchase_quantity_unit()
|
||||
line = PurchaseLine()
|
||||
self._set_ordered_purchase_line_values(line, quantity, unit)
|
||||
purchase = Purchase()
|
||||
purchase.lines = [line]
|
||||
purchase.party = self.supplier
|
||||
if purchase.party.addresses:
|
||||
purchase.invoice_address = purchase.party.addresses[0]
|
||||
purchase.currency = self.currency
|
||||
purchase.line_type = 'service'
|
||||
purchase.from_location, purchase.to_location = (
|
||||
self._get_ordered_purchase_locations())
|
||||
purchase.payment_term = self._get_ordered_purchase_payment_term()
|
||||
Purchase.save([purchase])
|
||||
self.purchase = purchase
|
||||
with Transaction().set_context(_purchase_trade_skip_fee_sync=True):
|
||||
self.__class__.save([self])
|
||||
self._create_fee_accruals()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
vlist = [x.copy() for x in vlist]
|
||||
fees = super(Fee, cls).create(vlist)
|
||||
qt_sh = Decimal(0)
|
||||
qt_line = Decimal(0)
|
||||
unit = None
|
||||
for fee in fees:
|
||||
FeeLots = Pool().get('fee.lots')
|
||||
Lots = Pool().get('lot.lot')
|
||||
LotQt = Pool().get('lot.qt')
|
||||
for fee in fees:
|
||||
qt_sh = Decimal(0)
|
||||
qt_line = Decimal(0)
|
||||
FeeLots = Pool().get('fee.lots')
|
||||
Lots = Pool().get('lot.lot')
|
||||
LotQt = Pool().get('lot.qt')
|
||||
if fee.line:
|
||||
for l in fee.line.lots:
|
||||
if (l.lot_type == 'virtual' and len(fee.line.lots)==1) or (l.lot_type == 'physic' and len(fee.line.lots)>1):
|
||||
@@ -871,63 +979,8 @@ class Fee(ModelSQL,ModelView):
|
||||
else:
|
||||
raise UserError("You cannot add fee on received shipment!")
|
||||
|
||||
type = fee.type
|
||||
if type == 'ordered':
|
||||
Purchase = Pool().get('purchase.purchase')
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
pl = PurchaseLine()
|
||||
pl.product = fee.product
|
||||
if fee.line or fee.sale_line:
|
||||
pl.quantity = round(qt_line,5)
|
||||
if fee.shipment_in:
|
||||
pl.quantity = round(qt_sh,5)
|
||||
logger.info("CREATE_PURHCASE_FOR_FEE_QT:%s",pl.quantity)
|
||||
pl.unit = unit
|
||||
pl.fee_ = fee.id
|
||||
if fee.price:
|
||||
fee_price = fee.get_price_per_qt()
|
||||
logger.info("GET_FEE_PRICE_PER_QT:%s",fee_price)
|
||||
pl.unit_price = round(Decimal(fee_price),4)
|
||||
if fee.mode == 'lumpsum':
|
||||
pl.quantity = 1
|
||||
pl.unit_price = round(Decimal(fee.amount),4)
|
||||
elif fee.mode == 'ppack':
|
||||
pl.unit_price = fee.price
|
||||
p = Purchase()
|
||||
p.lines = [pl]
|
||||
p.party = fee.supplier
|
||||
if p.party.addresses:
|
||||
p.invoice_address = p.party.addresses[0]
|
||||
p.currency = fee.currency
|
||||
p.line_type = 'service'
|
||||
p.from_location = fee.shipment_in.from_location if fee.shipment_in else (fee.line.purchase.from_location if fee.line else fee.sale_line.sale.from_location)
|
||||
p.to_location = fee.shipment_in.to_location if fee.shipment_in else (fee.line.purchase.to_location if fee.line else fee.sale_line.sale.to_location)
|
||||
if fee.shipment_in and fee.shipment_in.lotqt:
|
||||
p.payment_term = fee.shipment_in.lotqt[0].lot_p.line.purchase.payment_term
|
||||
elif fee.line:
|
||||
p.payment_term = fee.line.purchase.payment_term
|
||||
elif fee.sale_line:
|
||||
p.payment_term = fee.sale_line.sale.payment_term
|
||||
Purchase.save([p])
|
||||
#if reception of moves done we need to generate accrual for fee
|
||||
if not fee.sale_line:
|
||||
feelots = FeeLots.search(['fee','=',fee.id])
|
||||
for fl in feelots:
|
||||
if fee.product.template.landed_cost:
|
||||
move = fl.lot.get_received_move()
|
||||
if move:
|
||||
Warning = Pool().get('res.user.warning')
|
||||
warning_name = Warning.format("Lot ever received", [])
|
||||
if Warning.check(warning_name):
|
||||
raise UserWarning(warning_name,
|
||||
"By clicking yes, an accrual for this fee will be created")
|
||||
account_move = move._get_account_stock_move_fee(fee)
|
||||
cls._save_fee_accrual_or_warn(
|
||||
account_move, fee, fl.lot)
|
||||
else:
|
||||
account_move = fee._get_account_move_fee(fl.lot)
|
||||
cls._save_fee_accrual_or_warn(
|
||||
account_move, fee, fl.lot)
|
||||
logger.info("CREATE_PURHCASE_FOR_FEE_QT:%s", qt_line or qt_sh)
|
||||
fee.ensure_ordered_purchase()
|
||||
|
||||
for fee in fees:
|
||||
fee.sync_quantity_from_lots()
|
||||
@@ -947,6 +1000,7 @@ class Fee(ModelSQL,ModelView):
|
||||
fees = sum(args[::2], [])
|
||||
for fee in fees:
|
||||
fee.sync_quantity_from_lots()
|
||||
fee.ensure_ordered_purchase()
|
||||
fee.adjust_purchase_values()
|
||||
cls.assert_quantities_consistency(fees)
|
||||
purchase_lines, sale_lines = cls._collect_pnl_lines(fees=fees)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
import datetime
|
||||
from contextlib import nullcontext
|
||||
from decimal import Decimal
|
||||
from unittest.mock import ANY, Mock, patch
|
||||
from xml.etree import ElementTree
|
||||
@@ -2552,6 +2553,59 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertFalse(field.required)
|
||||
self.assertNotIn('required', field.states)
|
||||
|
||||
def test_fee_ensure_ordered_purchase_creates_missing_service_purchase(self):
|
||||
'ordered fee creates missing purchase after linked values are completed'
|
||||
Fee = Pool().get('fee.fee')
|
||||
fee = Fee()
|
||||
fee.id = 10
|
||||
fee.type = 'ordered'
|
||||
fee.purchase = None
|
||||
fee.currency = Mock()
|
||||
fee.price = Decimal('12')
|
||||
fee.mode = 'perqt'
|
||||
fee.product = Mock()
|
||||
fee.supplier = Mock(addresses=[])
|
||||
fee.quantity = Decimal('5')
|
||||
fee.unit = Mock()
|
||||
fee.shipment_in = None
|
||||
fee.sale_line = None
|
||||
fee.line = Mock(
|
||||
purchase=Mock(
|
||||
from_location=Mock(),
|
||||
to_location=Mock(),
|
||||
payment_term=Mock()))
|
||||
|
||||
purchase = Mock()
|
||||
purchase_model = Mock(return_value=purchase)
|
||||
line = Mock()
|
||||
line_model = Mock(return_value=line)
|
||||
|
||||
with patch(
|
||||
'trytond.modules.purchase_trade.fee.Pool'
|
||||
) as PoolMock, patch(
|
||||
'trytond.modules.purchase_trade.fee.Transaction'
|
||||
) as TransactionMock, patch.object(
|
||||
Fee, 'save') as save, patch.object(
|
||||
fee, '_get_effective_fee_lots', return_value=[]), patch.object(
|
||||
fee, '_create_fee_accruals') as create_accruals:
|
||||
PoolMock.return_value.get.side_effect = lambda name: {
|
||||
'purchase.purchase': purchase_model,
|
||||
'purchase.line': line_model,
|
||||
}[name]
|
||||
TransactionMock.return_value.set_context.return_value = (
|
||||
nullcontext())
|
||||
|
||||
self.assertTrue(fee.ensure_ordered_purchase())
|
||||
|
||||
self.assertEqual(fee.purchase, purchase)
|
||||
self.assertEqual(purchase.lines, [line])
|
||||
self.assertEqual(purchase.currency, fee.currency)
|
||||
self.assertEqual(line.fee_, fee.id)
|
||||
self.assertEqual(line.unit_price, Decimal('12.0000'))
|
||||
purchase_model.save.assert_called_once_with([purchase])
|
||||
save.assert_called_once_with([fee])
|
||||
create_accruals.assert_called_once_with()
|
||||
|
||||
def test_fee_get_non_cog_returns_zero_without_move_lines(self):
|
||||
'fee non-cog amount is zero before any accounting move line exists'
|
||||
fee = fee_module.Fee()
|
||||
|
||||
@@ -8,7 +8,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="mode"/>
|
||||
<field name="unit"/>
|
||||
<field name="auto_calculation" width="60"/>
|
||||
<field name="enable_linked_currency" width="60"/>
|
||||
<field name="enable_linked_currency" width="90"/>
|
||||
<field name="price"/>
|
||||
<field name="currency"/>
|
||||
<field name="weight_type"/>
|
||||
|
||||
@@ -8,7 +8,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="mode"/>
|
||||
<field name="unit"/>
|
||||
<field name="auto_calculation" width="60"/>
|
||||
<field name="enable_linked_currency" width="60"/>
|
||||
<field name="enable_linked_currency" width="90"/>
|
||||
<field name="price"/>
|
||||
<field name="currency"/>
|
||||
<field name="weight_type"/>
|
||||
|
||||
Reference in New Issue
Block a user