Calculate the quantities to ship and to invoice for sale and purchase
This commit is contained in:
@@ -75,6 +75,22 @@ class Line(order_line_mixin('sale'), metaclass=PoolMeta):
|
||||
def movable_types(cls):
|
||||
return super().movable_types() + ['kit']
|
||||
|
||||
def set_quantities(self):
|
||||
super().set_quantities()
|
||||
if self.components:
|
||||
def quantity(component):
|
||||
quantity = component.quantity_to_ship
|
||||
if component.fixed:
|
||||
quantity *= component.quantity / self.quantity
|
||||
else:
|
||||
quantity *= component.quantity_ratio
|
||||
return quantity
|
||||
quantity_to_ship = max(quantity(c) for c in self.components)
|
||||
if self.unit:
|
||||
quantity_to_ship = self.unit.ceil(quantity_to_ship)
|
||||
if quantity_to_ship > (self.quantity_to_ship or 0):
|
||||
self.quantity_to_ship = quantity_to_ship
|
||||
|
||||
|
||||
class LineComponent(
|
||||
order_line_component_mixin('sale'), ComponentMixin,
|
||||
@@ -96,6 +112,13 @@ class LineComponent(
|
||||
def warehouse(self):
|
||||
return self.line.warehouse
|
||||
|
||||
@property
|
||||
def quantity_to_ship(self):
|
||||
shipment_type = 'out' if self.quantity >= 0 else 'in'
|
||||
return self.unit.round(
|
||||
self._get_move_quantity(shipment_type)
|
||||
- self._get_shipped_quantity(shipment_type))
|
||||
|
||||
def get_move(self, shipment_type):
|
||||
from trytond.modules.sale.exceptions import PartyLocationError
|
||||
pool = Pool()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
* Calculate the quantities to invoice for purchase
|
||||
* Rename invoice method on shipment into on fulfillment
|
||||
* Add support for Python 3.14
|
||||
* Remove support for Python 3.9
|
||||
|
||||
@@ -199,6 +199,7 @@ class Purchase(
|
||||
('paid', 'Paid'),
|
||||
('exception', 'Exception'),
|
||||
], 'Invoice State', readonly=True, required=True, sort=False)
|
||||
to_invoice = fields.Boolean("To Invoice", readonly=True)
|
||||
invoices = fields.Function(fields.Many2Many(
|
||||
'account.invoice', None, None, "Invoices"),
|
||||
'get_invoices', searcher='search_invoices')
|
||||
@@ -338,9 +339,10 @@ class Purchase(
|
||||
},
|
||||
'manual_invoice': {
|
||||
'invisible': (
|
||||
(Eval('invoice_method') != 'manual')
|
||||
~Eval('to_invoice', False)
|
||||
| (Eval('invoice_method') != 'manual')
|
||||
| ~Eval('state').in_(['processing', 'done'])),
|
||||
'depends': ['invoice_method', 'state'],
|
||||
'depends': ['to_invoice', 'invoice_method', 'state'],
|
||||
},
|
||||
'handle_invoice_exception': {
|
||||
'invisible': ((Eval('invoice_state') != 'exception')
|
||||
@@ -761,6 +763,7 @@ class Purchase(
|
||||
default.setdefault('number', None)
|
||||
default.setdefault('reference')
|
||||
default.setdefault('invoice_state', 'none')
|
||||
default.setdefault('to_invoice')
|
||||
default.setdefault('invoices_ignored', None)
|
||||
default.setdefault('shipment_state', 'none')
|
||||
default.setdefault('purchase_date', None)
|
||||
@@ -1060,6 +1063,7 @@ class Purchase(
|
||||
pool = Pool()
|
||||
Line = pool.get('purchase.line')
|
||||
lines = []
|
||||
purchases_to_invoice = defaultdict(list)
|
||||
invoice_states, shipment_states = defaultdict(list), defaultdict(list)
|
||||
for purchase in purchases:
|
||||
invoice_state = purchase.get_invoice_state()
|
||||
@@ -1069,10 +1073,16 @@ class Purchase(
|
||||
if purchase.shipment_state != shipment_state:
|
||||
shipment_states[shipment_state].append(purchase)
|
||||
|
||||
to_invoice = False
|
||||
for line in purchase.line_lines:
|
||||
line.set_actual_quantity()
|
||||
line.set_quantities()
|
||||
to_invoice |= bool(line.quantity_to_invoice)
|
||||
lines.append(line)
|
||||
if purchase.to_invoice != to_invoice:
|
||||
purchases_to_invoice[to_invoice].append(purchase)
|
||||
|
||||
for to_invoice, purchases in purchases_to_invoice.items():
|
||||
cls.write(purchases, {'to_invoice': to_invoice})
|
||||
for invoice_state, purchases in invoice_states.items():
|
||||
cls.write(purchases, {'invoice_state': invoice_state})
|
||||
cls.log(purchases, 'transition', f'invoice_state:{invoice_state}')
|
||||
@@ -1188,6 +1198,11 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
|
||||
states={
|
||||
'invisible': ((Eval('type') != 'line') | ~Eval('actual_quantity')),
|
||||
})
|
||||
quantity_to_invoice = fields.Float(
|
||||
"Quantity to Invoice", digits='unit', readonly=True,
|
||||
states={
|
||||
'invisible': ~Eval('quantity_to_invoice'),
|
||||
})
|
||||
unit = fields.Many2One('product.uom', 'Unit',
|
||||
ondelete='RESTRICT',
|
||||
states={
|
||||
@@ -2069,7 +2084,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
|
||||
lambda l: samesign(self.quantity, l.quantity), lines)
|
||||
return list(lines)
|
||||
|
||||
def set_actual_quantity(self):
|
||||
def set_quantities(self):
|
||||
pool = Pool()
|
||||
Uom = pool.get('product.uom')
|
||||
if self.type != 'line':
|
||||
@@ -2097,6 +2112,14 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
|
||||
if self.actual_quantity != actual_quantity:
|
||||
self.actual_quantity = actual_quantity
|
||||
|
||||
quantity_to_invoice = (
|
||||
self._get_invoice_line_quantity()
|
||||
- self._get_invoiced_quantity())
|
||||
if self.unit:
|
||||
quantity_to_invoice = self.unit.round(quantity_to_invoice)
|
||||
if self.quantity_to_invoice != quantity_to_invoice:
|
||||
self.quantity_to_invoice = quantity_to_invoice
|
||||
|
||||
def get_rec_name(self, name):
|
||||
pool = Pool()
|
||||
Lang = pool.get('ir.lang')
|
||||
@@ -2159,6 +2182,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
|
||||
default.setdefault('moves_recreated', None)
|
||||
default.setdefault('invoice_lines', None)
|
||||
default.setdefault('actual_quantity')
|
||||
default.setdefault('quantity_to_invoice')
|
||||
return super().copy(lines, default=default)
|
||||
|
||||
|
||||
|
||||
@@ -4,13 +4,19 @@ Purchase Manual Invoice Scenario
|
||||
|
||||
Imports::
|
||||
|
||||
>>> import datetime as dt
|
||||
>>> from decimal import Decimal
|
||||
|
||||
>>> from proteus import Model
|
||||
>>> from trytond.modules.account.tests.tools import create_chart, get_accounts
|
||||
>>> from trytond.modules.account.tests.tools import (
|
||||
... create_chart, create_fiscalyear, get_accounts)
|
||||
>>> from trytond.modules.account_invoice.tests.tools import (
|
||||
... set_fiscalyear_invoice_sequences)
|
||||
>>> from trytond.modules.company.tests.tools import create_company
|
||||
>>> from trytond.tests.tools import activate_modules
|
||||
|
||||
>>> today = dt.date.today()
|
||||
|
||||
Activate modules::
|
||||
|
||||
>>> config = activate_modules('purchase', create_company, create_chart)
|
||||
@@ -25,6 +31,11 @@ Get accounts::
|
||||
|
||||
>>> accounts = get_accounts()
|
||||
|
||||
Create fiscal year::
|
||||
|
||||
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
|
||||
>>> fiscalyear.click('create_period')
|
||||
|
||||
Create party::
|
||||
|
||||
>>> supplier = Party(name='Supplier')
|
||||
@@ -67,6 +78,11 @@ Purchase with manual invoice method::
|
||||
'none'
|
||||
>>> len(purchase.invoices)
|
||||
0
|
||||
>>> bool(purchase.to_invoice)
|
||||
True
|
||||
>>> line, = purchase.lines
|
||||
>>> line.quantity_to_invoice
|
||||
10.0
|
||||
|
||||
Manually create an invoice::
|
||||
|
||||
@@ -75,16 +91,27 @@ Manually create an invoice::
|
||||
'processing'
|
||||
>>> purchase.invoice_state
|
||||
'pending'
|
||||
>>> bool(purchase.to_invoice)
|
||||
False
|
||||
>>> line, = purchase.lines
|
||||
>>> line.quantity_to_invoice
|
||||
0.0
|
||||
|
||||
Change quantity on invoice and create a new invoice::
|
||||
|
||||
>>> invoice, = purchase.invoices
|
||||
>>> invoice.invoice_date = today
|
||||
>>> line, = invoice.lines
|
||||
>>> line.quantity = 5
|
||||
>>> invoice.save()
|
||||
>>> invoice.click('post')
|
||||
>>> invoice.state
|
||||
'posted'
|
||||
|
||||
>>> purchase.reload()
|
||||
>>> len(purchase.invoices)
|
||||
1
|
||||
>>> bool(purchase.to_invoice)
|
||||
True
|
||||
>>> purchase.click('manual_invoice')
|
||||
>>> len(purchase.invoices)
|
||||
2
|
||||
|
||||
@@ -11,6 +11,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="summary" expand="1" optional="1"/>
|
||||
<field name="actual_quantity" symbol="unit" optional="0"/>
|
||||
<field name="quantity" symbol="unit" optional="0"/>
|
||||
<field name="quantity_to_invoice" symbol="unit" optional="1"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="amount"/>
|
||||
<field name="moves_progress" string="Shipping" widget="progressbar" optional="1"/>
|
||||
|
||||
@@ -8,6 +8,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="product_supplier" expand="1" optional="1"/>
|
||||
<field name="summary" expand="1" optional="1"/>
|
||||
<field name="quantity" symbol="unit"/>
|
||||
<field name="quantity_to_invoice" symbol="unit" optional="1"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="taxes" optional="0"/>
|
||||
<field name="amount"/>
|
||||
|
||||
@@ -13,5 +13,6 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="description" expand="1" optional="1"/>
|
||||
<field name="state"/>
|
||||
<field name="invoice_state"/>
|
||||
<field name="to_invoice" optional="1"/>
|
||||
<field name="shipment_state"/>
|
||||
</tree>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
* Calculate the quantities to ship and to invoice for sale
|
||||
* Rename invoice method on shipment into on fulfillment
|
||||
* Add support for Python 3.14
|
||||
* Remove support for Python 3.9
|
||||
|
||||
+58
-6
@@ -256,6 +256,7 @@ class Sale(
|
||||
('paid', 'Paid'),
|
||||
('exception', 'Exception'),
|
||||
], 'Invoice State', readonly=True, required=True, sort=False)
|
||||
to_invoice = fields.Boolean("To Invoice", readonly=True)
|
||||
invoices = fields.Function(fields.Many2Many(
|
||||
'account.invoice', None, None, "Invoices"),
|
||||
'get_invoices', searcher='search_invoices')
|
||||
@@ -288,6 +289,7 @@ class Sale(
|
||||
('sent', 'Sent'),
|
||||
('exception', 'Exception'),
|
||||
], "Shipment State", readonly=True, required=True, sort=False)
|
||||
to_ship = fields.Boolean("To Ship", readonly=True)
|
||||
shipments = fields.Function(fields.Many2Many(
|
||||
'stock.shipment.out', None, None, "Shipments"),
|
||||
'get_shipments', searcher='search_shipments')
|
||||
@@ -402,15 +404,17 @@ class Sale(
|
||||
},
|
||||
'manual_invoice': {
|
||||
'invisible': (
|
||||
(Eval('invoice_method') != 'manual')
|
||||
~Eval('to_invoice', False)
|
||||
| (Eval('invoice_method') != 'manual')
|
||||
| ~Eval('state').in_(['processing', 'done'])),
|
||||
'depends': ['invoice_method', 'state'],
|
||||
'depends': ['to_invoice', 'invoice_method', 'state'],
|
||||
},
|
||||
'manual_shipment': {
|
||||
'invisible': (
|
||||
(Eval('shipment_method') != 'manual')
|
||||
~Eval('to_ship', False)
|
||||
| (Eval('shipment_method') != 'manual')
|
||||
| ~Eval('state').in_(['processing', 'done'])),
|
||||
'depends': ['shipment_method', 'state'],
|
||||
'depends': ['to_ship', 'shipment_method', 'state'],
|
||||
},
|
||||
'handle_invoice_exception': {
|
||||
'invisible': ((Eval('invoice_state') != 'exception')
|
||||
@@ -900,8 +904,10 @@ class Sale(
|
||||
default.setdefault('number', None)
|
||||
default.setdefault('reference')
|
||||
default.setdefault('invoice_state', 'none')
|
||||
default.setdefault('to_invoice')
|
||||
default.setdefault('invoices_ignored', None)
|
||||
default.setdefault('shipment_state', 'none')
|
||||
default.setdefault('to_ship')
|
||||
default.setdefault('quotation_date')
|
||||
default.setdefault('sale_date', None)
|
||||
default.setdefault('quoted_by')
|
||||
@@ -1250,6 +1256,7 @@ class Sale(
|
||||
pool = Pool()
|
||||
Line = pool.get('sale.line')
|
||||
lines = []
|
||||
sales_to_invoice, sales_to_ship = defaultdict(list), defaultdict(list)
|
||||
invoice_states, shipment_states = defaultdict(list), defaultdict(list)
|
||||
for sale in sales:
|
||||
invoice_state = sale.get_invoice_state()
|
||||
@@ -1259,10 +1266,21 @@ class Sale(
|
||||
if sale.shipment_state != shipment_state:
|
||||
shipment_states[shipment_state].append(sale)
|
||||
|
||||
to_invoice = to_ship = False
|
||||
for line in sale.line_lines:
|
||||
line.set_actual_quantity()
|
||||
line.set_quantities()
|
||||
to_invoice |= bool(line.quantity_to_invoice)
|
||||
to_ship |= bool(line.quantity_to_ship)
|
||||
lines.append(line)
|
||||
if sale.to_invoice != to_invoice:
|
||||
sales_to_invoice[to_invoice].append(sale)
|
||||
if sale.to_ship != to_ship:
|
||||
sales_to_ship[to_ship].append(sale)
|
||||
|
||||
for to_invoice, sales in sales_to_invoice.items():
|
||||
cls.write(sales, {'to_invoice': to_invoice})
|
||||
for to_ship, sales in sales_to_ship.items():
|
||||
cls.write(sales, {'to_ship': to_ship})
|
||||
for invoice_state, sales in invoice_states.items():
|
||||
cls.write(sales, {'invoice_state': invoice_state})
|
||||
cls.log(sales, 'transition', f'invoice_state:{invoice_state}')
|
||||
@@ -1396,6 +1414,16 @@ class SaleLine(TaxableMixin, sequence_ordered(), ModelSQL, ModelView):
|
||||
states={
|
||||
'invisible': ((Eval('type') != 'line') | ~Eval('actual_quantity')),
|
||||
})
|
||||
quantity_to_ship = fields.Float(
|
||||
"Quantity to Ship", digits='unit', readonly=True,
|
||||
states={
|
||||
'invisible': ~Eval('quantity_to_ship'),
|
||||
})
|
||||
quantity_to_invoice = fields.Float(
|
||||
"Quantity to Invoice", digits='unit', readonly=True,
|
||||
states={
|
||||
'invisible': ~Eval('quantity_to_invoice'),
|
||||
})
|
||||
unit = fields.Many2One('product.uom', 'Unit', ondelete='RESTRICT',
|
||||
states={
|
||||
'required': Bool(Eval('product')),
|
||||
@@ -2192,9 +2220,11 @@ class SaleLine(TaxableMixin, sequence_ordered(), ModelSQL, ModelView):
|
||||
invoice_lines.append(invoice_line)
|
||||
return invoice_lines
|
||||
|
||||
def set_actual_quantity(self):
|
||||
def set_quantities(self):
|
||||
pool = Pool()
|
||||
Uom = pool.get('product.uom')
|
||||
Move = pool.get('stock.move')
|
||||
|
||||
if self.type != 'line':
|
||||
return
|
||||
moved_quantity = 0
|
||||
@@ -2220,6 +2250,26 @@ class SaleLine(TaxableMixin, sequence_ordered(), ModelSQL, ModelView):
|
||||
if self.actual_quantity != actual_quantity:
|
||||
self.actual_quantity = actual_quantity
|
||||
|
||||
if self.product and self.product.type in Move.get_product_types():
|
||||
shipment_type = 'out' if self.quantity >= 0 else 'in'
|
||||
quantity_to_ship = (
|
||||
self._get_move_quantity(shipment_type)
|
||||
- self._get_shipped_quantity(shipment_type))
|
||||
if self.unit:
|
||||
quantity_to_ship = self.unit.round(quantity_to_ship)
|
||||
else:
|
||||
quantity_to_ship = None
|
||||
if self.quantity_to_ship != quantity_to_ship:
|
||||
self.quantity_to_ship = quantity_to_ship
|
||||
|
||||
quantity_to_invoice = (
|
||||
self._get_invoice_line_quantity()
|
||||
- self._get_invoiced_quantity())
|
||||
if self.unit:
|
||||
quantity_to_invoice = self.unit.round(quantity_to_invoice)
|
||||
if self.quantity_to_invoice != quantity_to_invoice:
|
||||
self.quantity_to_invoice = quantity_to_invoice
|
||||
|
||||
def get_rec_name(self, name):
|
||||
pool = Pool()
|
||||
Lang = pool.get('ir.lang')
|
||||
@@ -2278,6 +2328,8 @@ class SaleLine(TaxableMixin, sequence_ordered(), ModelSQL, ModelView):
|
||||
default.setdefault('moves_recreated', None)
|
||||
default.setdefault('invoice_lines', None)
|
||||
default.setdefault('actual_quantity')
|
||||
default.setdefault('quantity_to_invoice')
|
||||
default.setdefault('quantity_to_ship')
|
||||
return super().copy(lines, default=default)
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ Imports::
|
||||
>>> from decimal import Decimal
|
||||
|
||||
>>> from proteus import Model
|
||||
>>> from trytond.modules.account.tests.tools import create_chart, get_accounts
|
||||
>>> from trytond.modules.account.tests.tools import (
|
||||
... create_chart, create_fiscalyear, get_accounts)
|
||||
>>> from trytond.modules.account_invoice.tests.tools import (
|
||||
... set_fiscalyear_invoice_sequences)
|
||||
>>> from trytond.modules.company.tests.tools import create_company
|
||||
>>> from trytond.tests.tools import activate_modules
|
||||
|
||||
@@ -25,6 +28,11 @@ Get accounts::
|
||||
|
||||
>>> accounts = get_accounts()
|
||||
|
||||
Create fiscal year::
|
||||
|
||||
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
|
||||
>>> fiscalyear.click('create_period')
|
||||
|
||||
Create party::
|
||||
|
||||
>>> customer = Party(name="Customer")
|
||||
@@ -67,6 +75,11 @@ Sale with manual invoice method::
|
||||
'none'
|
||||
>>> len(sale.invoices)
|
||||
0
|
||||
>>> bool(sale.to_invoice)
|
||||
True
|
||||
>>> line, = sale.lines
|
||||
>>> line.quantity_to_invoice
|
||||
10.0
|
||||
|
||||
Manually create an invoice::
|
||||
|
||||
@@ -75,16 +88,26 @@ Manually create an invoice::
|
||||
'processing'
|
||||
>>> sale.invoice_state
|
||||
'pending'
|
||||
>>> bool(sale.to_invoice)
|
||||
False
|
||||
>>> line, = sale.lines
|
||||
>>> line.quantity_to_invoice
|
||||
0.0
|
||||
|
||||
Change quantity on invoice and create a new invoice::
|
||||
|
||||
>>> invoice, = sale.invoices
|
||||
>>> line, = invoice.lines
|
||||
>>> line.quantity = 5
|
||||
>>> invoice.save()
|
||||
>>> invoice.click('post')
|
||||
>>> invoice.state
|
||||
'posted'
|
||||
|
||||
>>> sale.reload()
|
||||
>>> len(sale.invoices)
|
||||
1
|
||||
>>> bool(sale.to_invoice)
|
||||
True
|
||||
>>> sale.click('manual_invoice')
|
||||
>>> len(sale.invoices)
|
||||
2
|
||||
|
||||
@@ -53,6 +53,11 @@ Sale with manual shipment method::
|
||||
'processing'
|
||||
>>> len(sale.shipments)
|
||||
0
|
||||
>>> bool(sale.to_ship)
|
||||
True
|
||||
>>> line, = sale.lines
|
||||
>>> line.quantity_to_ship
|
||||
10.0
|
||||
|
||||
Manually create a shipment::
|
||||
|
||||
@@ -61,16 +66,29 @@ Manually create a shipment::
|
||||
'processing'
|
||||
>>> sale.shipment_state
|
||||
'waiting'
|
||||
>>> bool(sale.to_ship)
|
||||
False
|
||||
>>> line, = sale.lines
|
||||
>>> line.quantity_to_ship
|
||||
0.0
|
||||
|
||||
Change quantity on shipment and create a new shipment::
|
||||
|
||||
>>> shipment, = sale.shipments
|
||||
>>> move, = shipment.outgoing_moves
|
||||
>>> move, = shipment.inventory_moves
|
||||
>>> move.quantity = 5
|
||||
>>> shipment.save()
|
||||
>>> shipment.click('assign_force')
|
||||
>>> shipment.click('pick')
|
||||
>>> shipment.click('pack')
|
||||
>>> shipment.click('ship')
|
||||
>>> shipment.state
|
||||
'shipped'
|
||||
|
||||
>>> sale.reload()
|
||||
>>> len(sale.shipments)
|
||||
1
|
||||
>>> bool(sale.to_ship)
|
||||
True
|
||||
>>> sale.click('manual_shipment')
|
||||
>>> len(sale.shipments)
|
||||
2
|
||||
|
||||
@@ -10,6 +10,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="summary" expand="1" optional="1"/>
|
||||
<field name="actual_quantity" symbol="unit" optional="0"/>
|
||||
<field name="quantity" symbol="unit" optional="0"/>
|
||||
<field name="quantity_to_ship" symbol="unit" optional="1"/>
|
||||
<field name="quantity_to_invoice" symbol="unit" optional="1"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="amount"/>
|
||||
<field name="moves_progress" string="Shipping" widget="progressbar" optional="1"/>
|
||||
|
||||
@@ -7,6 +7,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="product" expand="1" optional="0"/>
|
||||
<field name="summary" expand="1" optional="1"/>
|
||||
<field name="quantity" symbol="unit"/>
|
||||
<field name="quantity_to_ship" symbol="unit" optional="1"/>
|
||||
<field name="quantity_to_invoice" symbol="unit" optional="1"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="taxes" optional="0"/>
|
||||
<field name="amount"/>
|
||||
|
||||
@@ -13,5 +13,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="description" expand="1" optional="1"/>
|
||||
<field name="state"/>
|
||||
<field name="invoice_state"/>
|
||||
<field name="to_invoice" optional="1"/>
|
||||
<field name="shipment_state"/>
|
||||
<field name="to_ship" optional="1"/>
|
||||
</tree>
|
||||
|
||||
Reference in New Issue
Block a user