Skip to content

Commit 53247a6

Browse files
authored
Accepted amount for a debit note/invoice is decided in the MarketStrategy (#954)
1 parent 3e774b9 commit 53247a6

File tree

5 files changed

+85
-16
lines changed

5 files changed

+85
-16
lines changed

docs/sphinx/api.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ Market strategies
101101
==========================
102102

103103
.. autoclass:: yapapi.strategy.MarketStrategy
104-
:members: decorate_demand, score_offer, respond_to_provider_offer, acceptable_prop_value_range_overrides, acceptable_prop_value_ranges
104+
:members: decorate_demand, score_offer, respond_to_provider_offer, acceptable_prop_value_range_overrides, acceptable_prop_value_ranges,
105+
invoice_accepted_amount, debit_note_accepted_amount
105106

106107
.. autoclass:: yapapi.strategy.WrappingMarketStrategy
107108
:members: __init__, base_strategy

yapapi/engine.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,9 @@ async def accept_payments_for_agreement(self, job_id: str, agreement_id: str) ->
400400

401401
async def _agreement_payment_attempt(self, agreement_id: str) -> None:
402402
invoice_manager = self._invoice_manager
403-
paid = await invoice_manager.attempt_payment(agreement_id, self._get_allocation)
403+
paid = await invoice_manager.attempt_payment(
404+
agreement_id, self._get_allocation, self._strategy.invoice_accepted_amount
405+
)
404406
if paid:
405407
# We've accepted the final invoice, so we can ignore debit notes
406408
job_id = invoice_manager.agreement_job(agreement_id).id
@@ -561,12 +563,25 @@ async def _process_debit_note(self, debit_note_id: str) -> None:
561563

562564
try:
563565
allocation = self._get_allocation(debit_note)
564-
await debit_note.accept(amount=debit_note.total_amount_due, allocation=allocation)
565-
job.emit(
566-
events.DebitNoteAccepted,
567-
agreement=agreement,
568-
debit_note=debit_note,
569-
)
566+
accepted_amount = await self._strategy.debit_note_accepted_amount(debit_note)
567+
if accepted_amount >= Decimal(debit_note.total_amount_due):
568+
await debit_note.accept(
569+
amount=debit_note.total_amount_due, allocation=allocation
570+
)
571+
job.emit(
572+
events.DebitNoteAccepted,
573+
agreement=agreement,
574+
debit_note=debit_note,
575+
)
576+
else:
577+
# We should reject the debit note, but it's not implemented in yagna,
578+
# so we just ignore it now
579+
logger.warning(
580+
"Ignored debit note %s for %s, we accept only %s",
581+
debit_note.debit_note_id,
582+
debit_note.total_amount_due,
583+
accepted_amount,
584+
)
570585
except CancelledError:
571586
raise
572587
except Exception:

yapapi/invoice_manager.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from asyncio import CancelledError
22
from dataclasses import dataclass
3-
from typing import Callable, Dict, Optional, Set, TYPE_CHECKING
3+
from decimal import Decimal
4+
from typing import Awaitable, Callable, Dict, Optional, Set, TYPE_CHECKING
45
import sys
56

67
from yapapi import events
@@ -20,6 +21,11 @@ class AgreementData:
2021
paid: bool = False
2122

2223

24+
import logging
25+
26+
logger = logging.getLogger(__name__)
27+
28+
2329
class InvoiceManager:
2430
def __init__(self):
2531
self._agreement_data: Dict[str, AgreementData] = {}
@@ -71,7 +77,10 @@ def set_payable(self, agreement_id: str) -> None:
7177
self._agreement_data[agreement_id].payable = True
7278

7379
async def attempt_payment(
74-
self, agreement_id: str, get_allocation: Callable[["Invoice"], "Allocation"]
80+
self,
81+
agreement_id: str,
82+
get_allocation: Callable[["Invoice"], "Allocation"],
83+
get_accepted_amount: Callable[["Invoice"], Awaitable[Decimal]],
7584
) -> bool:
7685
ad = self._agreement_data.get(agreement_id)
7786
if not ad or not ad.invoice or not ad.payable or ad.paid:
@@ -80,12 +89,23 @@ async def attempt_payment(
8089
invoice = ad.invoice
8190
try:
8291
allocation = get_allocation(invoice)
83-
await invoice.accept(amount=invoice.amount, allocation=allocation)
84-
ad.job.emit(
85-
events.InvoiceAccepted,
86-
agreement=ad.agreement,
87-
invoice=invoice,
88-
)
92+
accepted_amount = await get_accepted_amount(invoice)
93+
if accepted_amount >= Decimal(invoice.amount):
94+
await invoice.accept(amount=accepted_amount, allocation=allocation)
95+
ad.job.emit(
96+
events.InvoiceAccepted,
97+
agreement=ad.agreement,
98+
invoice=invoice,
99+
)
100+
else:
101+
# We should reject the invoice, but it's not implemented in yagna,
102+
# so we just ignore it now
103+
logger.warning(
104+
"Ignored invoice %s for %s, we accept only %s",
105+
invoice.invoice_id,
106+
invoice.amount,
107+
accepted_amount,
108+
)
89109
except CancelledError:
90110
raise
91111
except Exception:

yapapi/strategy/base.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import abc
44
from copy import deepcopy
55
from datetime import datetime, timezone
6+
from decimal import Decimal
67
import logging
78
from typing import Dict, Optional
89

@@ -106,6 +107,23 @@ async def respond_to_provider_offer(
106107
"""Respond with a modified `DemandBuilder` object to an offer coming from a provider."""
107108
raise NotImplementedError()
108109

110+
@abc.abstractmethod
111+
async def invoice_accepted_amount(self, invoice: rest.payment.Invoice) -> Decimal:
112+
"""Return the amount we accept to pay for the invoice.
113+
114+
Current Golem Engine implementation accepts the invoice if returned amount is not lower than
115+
`invoice.amount` and ignores the invoice otherwise. This will change in the future."""
116+
raise NotImplementedError()
117+
118+
@abc.abstractmethod
119+
async def debit_note_accepted_amount(self, debit_note: rest.payment.DebitNote) -> Decimal:
120+
"""Return the amount we accept to pay for the debit note.
121+
122+
Current Golem Engine implementation accepts the debit note if returned amount is not lower than
123+
`debit_note.total_amount_due` and ignores the debit note otherwise.
124+
This will change in the future."""
125+
raise NotImplementedError()
126+
109127

110128
class MarketStrategy(BaseMarketStrategy, abc.ABC):
111129
"""Abstract market strategy."""
@@ -207,3 +225,11 @@ async def respond_to_provider_offer(
207225

208226
async def decorate_demand(self, demand: DemandBuilder) -> None:
209227
"""Optionally add relevant constraints to a Demand."""
228+
229+
async def invoice_accepted_amount(self, invoice: rest.payment.Invoice) -> Decimal:
230+
"""Accept full invoice amount."""
231+
return Decimal(invoice.amount)
232+
233+
async def debit_note_accepted_amount(self, debit_note: rest.payment.DebitNote) -> Decimal:
234+
"""Accept full debit note amount."""
235+
return Decimal(debit_note.total_amount_due)

yapapi/strategy/wrapping_strategy.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import abc
2+
from decimal import Decimal
23

34
from yapapi.props.builder import DemandBuilder
45
from yapapi import rest
@@ -33,6 +34,12 @@ async def decorate_demand(self, demand: DemandBuilder) -> None:
3334
async def score_offer(self, offer: rest.market.OfferProposal) -> float:
3435
return await self.base_strategy.score_offer(offer)
3536

37+
async def invoice_accepted_amount(self, invoice: rest.payment.Invoice) -> Decimal:
38+
return await self.base_strategy.invoice_accepted_amount(invoice)
39+
40+
async def debit_note_accepted_amount(self, debit_note: rest.payment.DebitNote) -> Decimal:
41+
return await self.base_strategy.debit_note_accepted_amount(debit_note)
42+
3643
async def respond_to_provider_offer(
3744
self,
3845
our_demand: DemandBuilder,

0 commit comments

Comments
 (0)