1
1
from decimal import Decimal
2
- from typing import TYPE_CHECKING , List , Dict , Iterable , Optional , Union , Tuple
2
+ from typing import TYPE_CHECKING , List , Optional , Union , Tuple
3
3
import logging
4
4
5
5
from stdnum .eu import vat
6
6
import stdnum .exceptions
7
7
8
- from prices import TaxedMoney , TaxedMoneyRange
9
- from saleor .checkout .interface import CheckoutTaxedPricesData
10
- from saleor .core .taxes import include_taxes_in_prices
8
+ from prices import TaxedMoney , TaxedMoneyRange , Money
11
9
from saleor .order .interface import OrderTaxedPricesData
12
10
from saleor .plugins .base_plugin import BasePlugin
13
- from saleor .plugins . manager import get_plugins_manager
11
+ from saleor .tax . calculations . checkout import update_checkout_prices_with_flat_rates
14
12
15
13
16
14
if TYPE_CHECKING :
17
15
from saleor .account .models import Address
18
16
from saleor .checkout .fetch import CheckoutInfo , CheckoutLineInfo
19
17
from saleor .checkout .models import Checkout
20
- from saleor .discount import DiscountInfo
21
- from saleor .plugins .models import PluginConfiguration
22
18
23
19
24
20
logger = logging .getLogger (__name__ )
@@ -42,6 +38,7 @@ class VatReverseCharge(BasePlugin):
42
38
Read more at
43
39
https://europa.eu/youreurope/business/taxation/vat/cross-border-vat/index_en.htm#withintheeu
44
40
"""
41
+
45
42
PLUGIN_ID = "blender.saleor_vatrc"
46
43
PLUGIN_NAME = "VAT reverse charge"
47
44
PLUGIN_DESCRIPTION = (
@@ -51,46 +48,23 @@ class VatReverseCharge(BasePlugin):
51
48
META_VATIN_KEY = "vatrc.vatin"
52
49
META_VATIN_VALIDATED_KEY = "vatrc.vatin_validated"
53
50
54
- # This plugin depends on another plugin for calculating VAT
55
- VAT_PLUGIN_ID = 'mirumee.taxes.vatlayer'
56
-
57
51
CONFIGURATION_PER_CHANNEL = True
58
52
DEFAULT_ACTIVE = False
59
53
60
- @property
61
- def vat_plugin (self ) -> "PluginConfiguration" :
62
- manager = get_plugins_manager ()
63
- plugins = manager .plugins_per_channel [self .channel .slug ]
64
- return next ((p for p in plugins if p .PLUGIN_ID == self .VAT_PLUGIN_ID ), None )
65
-
66
- @property
67
- def vat_plugin_config (self ) -> Dict [str , str ]:
68
- # Convert to dict to easier get config elements of the plugin we depend on
69
- return {
70
- item ["name" ]: item ["value" ] for item in self .vat_plugin .configuration
71
- }
72
-
73
54
def _skip_plugin (
74
55
self ,
75
56
checkout_info : "CheckoutInfo" ,
76
57
previous_value : Union [
77
58
TaxedMoney ,
78
59
TaxedMoneyRange ,
79
60
Decimal ,
80
- CheckoutTaxedPricesData ,
81
61
OrderTaxedPricesData ,
82
62
],
83
63
) -> bool :
84
64
if not self .active :
85
65
return True
86
66
87
- # Skip when plugin that calculates VAT isn't active
88
- if not self .vat_plugin .active :
89
- return True
90
-
91
- # If taxes aren't included into prices, there's no reverse charge to apply.
92
- if not include_taxes_in_prices ():
93
- return True
67
+ return False
94
68
95
69
def _skip_price_modification (
96
70
self ,
@@ -99,28 +73,21 @@ def _skip_price_modification(
99
73
TaxedMoney ,
100
74
TaxedMoneyRange ,
101
75
Decimal ,
102
- CheckoutTaxedPricesData ,
103
76
OrderTaxedPricesData ,
104
77
],
105
78
) -> bool :
106
79
# If there's no tax on the given prices
107
80
if isinstance (previous_value , TaxedMoney ):
108
81
return previous_value .net == previous_value .gross
109
- if isinstance (previous_value , CheckoutTaxedPricesData ):
110
- return (
111
- previous_value .price_with_sale .net
112
- == previous_value .price_with_sale .gross
113
- )
114
82
if isinstance (previous_value , OrderTaxedPricesData ):
115
83
return (
116
- previous_value .price_with_discounts .net
117
- == previous_value .price_with_discounts .gross
84
+ previous_value .price_with_discounts .net == previous_value .price_with_discounts .gross
118
85
)
119
86
120
87
return False
121
88
122
- def _get_seller_country_code (self ) -> str :
123
- return self . vat_plugin_config . get ( "origin_country" , "" ). upper ()
89
+ def _get_seller_country_code (self , checkout ) -> str :
90
+ return checkout . channel . default_country . code
124
91
125
92
def _get_buyer_country_code (self , address : Optional ["Address" ]) -> str :
126
93
return address .country .code if address else ""
@@ -154,8 +121,10 @@ def _validate_vatin_metadata(
154
121
checkout : "Checkout" ,
155
122
address : Optional ["Address" ],
156
123
) -> bool :
157
- vatin_metadata_value = checkout .get_value_from_metadata (self .META_VATIN_KEY )
158
- valid_vatin_previous = checkout .get_value_from_metadata (
124
+ vatin_metadata_value = checkout .metadata_storage .get_value_from_metadata (
125
+ self .META_VATIN_KEY
126
+ )
127
+ valid_vatin_previous = checkout .metadata_storage .get_value_from_metadata (
159
128
self .META_VATIN_VALIDATED_KEY
160
129
)
161
130
buyer_country = self ._get_buyer_country_code (address )
@@ -167,18 +136,18 @@ def _validate_vatin_metadata(
167
136
# Does not look like a valid VATIN
168
137
if not vatin_country or not vatin or vatin_country != buyer_country :
169
138
logger .debug ('Invalid VATIN format: missing or mismatching country code' )
170
- checkout .delete_value_from_metadata (self .META_VATIN_KEY )
171
- checkout .delete_value_from_metadata (self .META_VATIN_VALIDATED_KEY )
172
- checkout .save (update_fields = ["metadata" ])
139
+ checkout .metadata_storage . delete_value_from_metadata (self .META_VATIN_KEY )
140
+ checkout .metadata_storage . delete_value_from_metadata (self .META_VATIN_VALIDATED_KEY )
141
+ checkout .metadata_storage . save (update_fields = ["metadata" ])
173
142
# Only validate the VATIN further if it differs from an already validated one:
174
143
elif vatin != valid_vatin_previous and self ._validate_vatin_value (vatin ):
175
144
logger .debug ('Updating VATIN: %s' , vatin )
176
145
metadata_items = {
177
146
self .META_VATIN_KEY : vatin ,
178
147
self .META_VATIN_VALIDATED_KEY : vatin ,
179
148
}
180
- checkout .store_value_in_metadata (items = metadata_items )
181
- checkout .save (update_fields = ["metadata" ])
149
+ checkout .metadata_storage . store_value_in_metadata (items = metadata_items )
150
+ checkout .metadata_storage . save (update_fields = ["metadata" ])
182
151
183
152
def _deduct_tax (self , previous_value : "TaxedMoney" ) -> "TaxedMoney" :
184
153
return TaxedMoney (gross = previous_value .net , net = previous_value .net )
@@ -188,61 +157,32 @@ def calculate_checkout_total(
188
157
checkout_info : "CheckoutInfo" ,
189
158
lines : List ["CheckoutLineInfo" ],
190
159
address : Optional ["Address" ],
191
- discounts : Iterable ["DiscountInfo" ],
192
160
previous_value : TaxedMoney ,
193
161
) -> TaxedMoney :
162
+
194
163
if self ._skip_plugin (checkout_info , previous_value ):
195
164
return previous_value
196
165
197
166
checkout = checkout_info .checkout
198
167
199
168
self ._validate_vatin_metadata (checkout , address )
200
169
201
- if self ._skip_price_modification (checkout_info , previous_value ):
202
- return previous_value
203
-
204
- valid_vatin = checkout .get_value_from_metadata (self .META_VATIN_VALIDATED_KEY )
170
+ valid_vatin = checkout .metadata_storage .get_value_from_metadata (
171
+ self .META_VATIN_VALIDATED_KEY
172
+ )
205
173
buyer_country_code = self ._get_buyer_country_code (address )
206
- seller_country_code = self ._get_seller_country_code ()
174
+ seller_country_code = self ._get_seller_country_code (checkout )
207
175
# If a valid VATIN is provided and the sale isn't within the same country,
208
176
# reverse-charged applies.
209
177
if valid_vatin and seller_country_code != buyer_country_code :
210
178
# VAT is reverse-charged, so it must be excluded from total.
211
179
return self ._deduct_tax (previous_value )
212
180
213
- return previous_value
214
-
215
- def calculate_checkout_line_total (
216
- self ,
217
- checkout_info : "CheckoutInfo" ,
218
- lines : List ["CheckoutLineInfo" ],
219
- checkout_line_info : "CheckoutLineInfo" ,
220
- address : Optional ["Address" ],
221
- discounts : Iterable ["DiscountInfo" ],
222
- previous_value : CheckoutTaxedPricesData ,
223
- ) -> CheckoutTaxedPricesData :
224
- if self ._skip_plugin (checkout_info , previous_value ):
225
- return previous_value
226
-
227
- checkout = checkout_info .checkout
228
-
229
- self ._validate_vatin_metadata (checkout , address )
230
-
231
- if self ._skip_price_modification (checkout_info , previous_value ):
232
- return previous_value
233
-
234
- valid_vatin = checkout .get_value_from_metadata (self .META_VATIN_VALIDATED_KEY )
235
- buyer_country_code = self ._get_buyer_country_code (address )
236
- seller_country_code = self ._get_seller_country_code ()
237
- # If a valid VATIN is provided and the sale isn't within the same country,
238
- # reverse-charged applies.
239
- if valid_vatin and seller_country_code != buyer_country_code :
240
- # VAT is reverse-charged, so it must be excluded from line total.
241
- return CheckoutTaxedPricesData (
242
- price_with_discounts = self ._deduct_tax (
243
- previous_value .price_with_discounts
244
- ),
245
- price_with_sale = self ._deduct_tax (previous_value .price_with_sale ),
246
- undiscounted_price = self ._deduct_tax (previous_value .undiscounted_price ),
247
- )
248
- return previous_value
181
+ update_checkout_prices_with_flat_rates (
182
+ checkout ,
183
+ checkout_info ,
184
+ lines ,
185
+ True ,
186
+ address ,
187
+ )
188
+ return checkout .total
0 commit comments