[IMP] nfu_sale_order_batch_packaging: add automatic fillup
This commit is contained in:
parent
a9ade6a690
commit
c2ded5f96f
9 changed files with 132 additions and 89 deletions
|
|
@ -33,23 +33,24 @@ and adds a packaging default to sale order batch.
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
- 0.0.1: Initial module.
|
- 16.0.0.0.1: Initial module.
|
||||||
- 1.0.0: rename to nfu_sale_order_batch_packaging
|
- 16.0.1.0.0: rename to nfu_sale_order_batch_packaging
|
||||||
- 1.1.0: Live
|
- 16.0.1.16.0.1.0: Live
|
||||||
- 1.2.0: Add regeneration action for batch products
|
- 16.0.1.2.0: Add regeneration action for batch products
|
||||||
- 1.2.1: refactoring
|
- 16.0.1.2.1: refactoring
|
||||||
- 1.3.0: add packaging states
|
- 16.0.1.3.0: add packaging states
|
||||||
- 1.3.1: add open_max_qty
|
- 16.0.1.3.1: add open_max_qty
|
||||||
- 1.3.2: fix color indicator
|
- 16.0.1.3.2: fix color indicator
|
||||||
- 1.3.3: Enable packagings on installation
|
- 16.0.1.3.3: Enable packagings on installation
|
||||||
- 1.3.4:
|
- 16.0.1.3.4:
|
||||||
- on creation set max_qty to product_uom_qty if not set
|
- on creation set max_qty to product_uom_qty if not set
|
||||||
- remove zero exception for max_qty
|
- remove zero exception for max_qty
|
||||||
- store open_packaging_qty on batch product module to make it sortable
|
- store open_packaging_qty on batch product module to make it sortable
|
||||||
- always adjust max_qty to be at least uom_qty except you only set max qty
|
- always adjust max_qty to be at least uom_qty except you only set max qty
|
||||||
- 1.3.4: store all quantities and round open values
|
- 16.0.1.3.4: store all quantities and round open values
|
||||||
- 1.3.6: make sure packages are readable by the user
|
- 16.0.1.3.6: make sure packages are readable by the user
|
||||||
- 1.3.7: really make sure packages are readable by the user
|
- 16.0.1.3.7: really make sure packages are readable by the user
|
||||||
|
- 16.0.1.4.0: Add automatiok batch fillup
|
||||||
|
|
||||||
Bug Tracker
|
Bug Tracker
|
||||||
===========
|
===========
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"author": "BAKEUP",
|
"author": "BAKEUP",
|
||||||
"website": "https://www.bakeup.org",
|
"website": "https://www.bakeup.org",
|
||||||
"category": "Sale",
|
"category": "Sale",
|
||||||
"version": "16.0.1.3.7",
|
"version": "16.0.1.4.0",
|
||||||
"depends": ["sale", "sale_order_batch"],
|
"depends": ["sale", "sale_order_batch"],
|
||||||
"data": [
|
"data": [
|
||||||
"security/ir.model.access.csv",
|
"security/ir.model.access.csv",
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,11 @@ class SaleOrderBatch(models.Model):
|
||||||
for line in batch.sale_order_line_ids
|
for line in batch.sale_order_line_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends("product_ids.open_packaging_qty", "product_ids.open_packaging_max_qty")
|
@api.depends("product_ids.open_packaging_qty")
|
||||||
def _compute_has_fillable_packages(self):
|
def _compute_has_fillable_packages(self):
|
||||||
for batch in self:
|
for batch in self:
|
||||||
batch.has_fillable_packages = any(
|
batch.has_fillable_packages = any(
|
||||||
p.open_packaging_qty > 0 and p.open_packaging_max_qty == 0.0
|
p.open_packaging_qty > 0 for p in batch.product_ids
|
||||||
for p in batch.product_ids
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def action_restore_ordered_qty(self):
|
def action_restore_ordered_qty(self):
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
- 0.0.1: Initial module.
|
- 16.0.0.0.1: Initial module.
|
||||||
- 1.0.0: rename to nfu_sale_order_batch_packaging
|
- 16.0.1.0.0: rename to nfu_sale_order_batch_packaging
|
||||||
- 1.1.0: Live
|
- 16.0.1.16.0.1.0: Live
|
||||||
- 1.2.0: Add regeneration action for batch products
|
- 16.0.1.2.0: Add regeneration action for batch products
|
||||||
- 1.2.1: refactoring
|
- 16.0.1.2.1: refactoring
|
||||||
- 1.3.0: add packaging states
|
- 16.0.1.3.0: add packaging states
|
||||||
- 1.3.1: add open_max_qty
|
- 16.0.1.3.1: add open_max_qty
|
||||||
- 1.3.2: fix color indicator
|
- 16.0.1.3.2: fix color indicator
|
||||||
- 1.3.3: Enable packagings on installation
|
- 16.0.1.3.3: Enable packagings on installation
|
||||||
- 1.3.4:
|
- 16.0.1.3.4:
|
||||||
- on creation set max_qty to product_uom_qty if not set
|
- on creation set max_qty to product_uom_qty if not set
|
||||||
- remove zero exception for max_qty
|
- remove zero exception for max_qty
|
||||||
- store open_packaging_qty on batch product module to make it sortable
|
- store open_packaging_qty on batch product module to make it sortable
|
||||||
- always adjust max_qty to be at least uom_qty except you only set max qty
|
- always adjust max_qty to be at least uom_qty except you only set max qty
|
||||||
- 1.3.4: store all quantities and round open values
|
- 16.0.1.3.4: store all quantities and round open values
|
||||||
- 1.3.6: make sure packages are readable by the user
|
- 16.0.1.3.6: make sure packages are readable by the user
|
||||||
- 1.3.7: really make sure packages are readable by the user
|
- 16.0.1.3.7: really make sure packages are readable by the user
|
||||||
|
- 16.0.1.4.0: Add automatiok batch fillup
|
||||||
|
|
@ -388,17 +388,17 @@ and adds a packaging default to sale order batch.</p>
|
||||||
<div class="section" id="changelog">
|
<div class="section" id="changelog">
|
||||||
<h1><a class="toc-backref" href="#toc-entry-1">Changelog</a></h1>
|
<h1><a class="toc-backref" href="#toc-entry-1">Changelog</a></h1>
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li>0.0.1: Initial module.</li>
|
<li>16.0.0.0.1: Initial module.</li>
|
||||||
<li>1.0.0: rename to nfu_sale_order_batch_packaging</li>
|
<li>16.0.1.0.0: rename to nfu_sale_order_batch_packaging</li>
|
||||||
<li>1.1.0: Live</li>
|
<li>16.0.1.16.0.1.0: Live</li>
|
||||||
<li>1.2.0: Add regeneration action for batch products</li>
|
<li>16.0.1.2.0: Add regeneration action for batch products</li>
|
||||||
<li>1.2.1: refactoring</li>
|
<li>16.0.1.2.1: refactoring</li>
|
||||||
<li>1.3.0: add packaging states</li>
|
<li>16.0.1.3.0: add packaging states</li>
|
||||||
<li>1.3.1: add open_max_qty</li>
|
<li>16.0.1.3.1: add open_max_qty</li>
|
||||||
<li>1.3.2: fix color indicator</li>
|
<li>16.0.1.3.2: fix color indicator</li>
|
||||||
<li>1.3.3: Enable packagings on installation</li>
|
<li>16.0.1.3.3: Enable packagings on installation</li>
|
||||||
<li><dl class="first docutils">
|
<li><dl class="first docutils">
|
||||||
<dt>1.3.4:</dt>
|
<dt>16.0.1.3.4:</dt>
|
||||||
<dd><ul class="first last">
|
<dd><ul class="first last">
|
||||||
<li>on creation set max_qty to product_uom_qty if not set</li>
|
<li>on creation set max_qty to product_uom_qty if not set</li>
|
||||||
<li>remove zero exception for max_qty</li>
|
<li>remove zero exception for max_qty</li>
|
||||||
|
|
@ -408,9 +408,10 @@ and adds a packaging default to sale order batch.</p>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</li>
|
</li>
|
||||||
<li>1.3.4: store all quantities and round open values</li>
|
<li>16.0.1.3.4: store all quantities and round open values</li>
|
||||||
<li>1.3.6: make sure packages are readable by the user</li>
|
<li>16.0.1.3.6: make sure packages are readable by the user</li>
|
||||||
<li>1.3.7: really make sure packages are readable by the user</li>
|
<li>16.0.1.3.7: really make sure packages are readable by the user</li>
|
||||||
|
<li>16.0.1.4.0: Add automatiok batch fillup</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="section" id="bug-tracker">
|
<div class="section" id="bug-tracker">
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,9 @@ class TestBatchPackagingReconcile(TransactionCase):
|
||||||
bp._reconcile_packaging(0.001)
|
bp._reconcile_packaging(0.001)
|
||||||
|
|
||||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||||
capped = bp.sale_order_line_ids.filtered(lambda l: l.product_uom_max_qty == 9.0)
|
capped = bp.sale_order_line_ids.filtered(
|
||||||
|
lambda line: line.product_uom_max_qty == 9.0
|
||||||
|
)
|
||||||
# capped line must be at its max
|
# capped line must be at its max
|
||||||
self.assertAlmostEqual(capped.product_uom_qty, 9.0, places=3)
|
self.assertAlmostEqual(capped.product_uom_qty, 9.0, places=3)
|
||||||
# every line must stay within its max
|
# every line must stay within its max
|
||||||
|
|
@ -96,7 +98,7 @@ class TestBatchPackagingReconcile(TransactionCase):
|
||||||
# total = 7, open = 5; rooms: 3 and 5
|
# total = 7, open = 5; rooms: 3 and 5
|
||||||
bp = self._add_lines(product, [(3.0, 6.0), (4.0, 9.0)])
|
bp = self._add_lines(product, [(3.0, 6.0), (4.0, 9.0)])
|
||||||
self.assertAlmostEqual(bp.open_packaging_qty, 5.0, places=2)
|
self.assertAlmostEqual(bp.open_packaging_qty, 5.0, places=2)
|
||||||
orig = {l.id: l.product_uom_qty for l in bp.sale_order_line_ids}
|
orig = {line.id: line.product_uom_qty for line in bp.sale_order_line_ids}
|
||||||
|
|
||||||
bp._reconcile_packaging(1.0)
|
bp._reconcile_packaging(1.0)
|
||||||
|
|
||||||
|
|
@ -111,7 +113,7 @@ class TestBatchPackagingReconcile(TransactionCase):
|
||||||
# total = 7, open = 3; rooms: 7 and 6 → sorted: line2(6), line1(7)
|
# total = 7, open = 3; rooms: 7 and 6 → sorted: line2(6), line1(7)
|
||||||
bp = self._add_lines(product, [(3.0, 10.0), (4.0, 10.0)])
|
bp = self._add_lines(product, [(3.0, 10.0), (4.0, 10.0)])
|
||||||
self.assertAlmostEqual(bp.open_packaging_qty, 3.0, places=2)
|
self.assertAlmostEqual(bp.open_packaging_qty, 3.0, places=2)
|
||||||
orig = {l.id: l.product_uom_qty for l in bp.sale_order_line_ids}
|
orig = {line.id: line.product_uom_qty for line in bp.sale_order_line_ids}
|
||||||
|
|
||||||
bp._reconcile_packaging(0.1)
|
bp._reconcile_packaging(0.1)
|
||||||
|
|
||||||
|
|
@ -230,8 +232,8 @@ class TestBatchPackagingReconcile(TransactionCase):
|
||||||
# reconciliation must have increased some qty
|
# reconciliation must have increased some qty
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
any(
|
any(
|
||||||
l.product_uom_qty != l.product_uom_ordered_qty
|
line.product_uom_qty != line.product_uom_ordered_qty
|
||||||
for l in bp.sale_order_line_ids
|
for line in bp.sale_order_line_ids
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.batch.action_restore_ordered_qty()
|
self.batch.action_restore_ordered_qty()
|
||||||
|
|
@ -258,8 +260,8 @@ class TestBatchPackagingReconcile(TransactionCase):
|
||||||
# product_b lines must be unchanged
|
# product_b lines must be unchanged
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
any(
|
any(
|
||||||
l.product_uom_qty != l.product_uom_ordered_qty
|
line.product_uom_qty != line.product_uom_ordered_qty
|
||||||
for l in bp_b.sale_order_line_ids
|
for line in bp_b.sale_order_line_ids
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -284,6 +286,32 @@ class TestBatchPackagingReconcile(TransactionCase):
|
||||||
self.batch.action_restore_ordered_qty()
|
self.batch.action_restore_ordered_qty()
|
||||||
self.assertFalse(self.batch.has_qty_adjusted)
|
self.assertFalse(self.batch.has_qty_adjusted)
|
||||||
|
|
||||||
|
def test_has_fillable_packages_false_when_no_open_qty(self):
|
||||||
|
"""has_fillable_packages is False when all products have complete packages."""
|
||||||
|
product = self._make_product(self.uom_kg, 10.0)
|
||||||
|
self._add_lines(product, [(10.0, 15.0)]) # qty is exact multiple of packaging
|
||||||
|
self.assertFalse(self.batch.has_fillable_packages)
|
||||||
|
|
||||||
|
def test_has_fillable_packages_true_for_eligible(self):
|
||||||
|
"""has_fillable_packages is True when an eligible product has open packaging qty."""
|
||||||
|
product = self._make_product(self.uom_kg, 10.0)
|
||||||
|
self._add_lines(product, [(23.0, 30.0)]) # open_packaging_max_qty == 0
|
||||||
|
self.assertTrue(self.batch.has_fillable_packages)
|
||||||
|
|
||||||
|
def test_has_fillable_packages_true_for_ineligible(self):
|
||||||
|
"""has_fillable_packages is True even for ineligible (zero-out) products."""
|
||||||
|
product = self._make_product(self.uom_kg, 10.0)
|
||||||
|
self._add_lines(
|
||||||
|
product, [(23.0, 24.0)]
|
||||||
|
) # max too tight → open_packaging_max_qty != 0
|
||||||
|
self.assertGreater(
|
||||||
|
self.batch.product_ids.filtered(
|
||||||
|
lambda p: p.product_id == product
|
||||||
|
).open_packaging_max_qty,
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
self.assertTrue(self.batch.has_fillable_packages)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Zero-out
|
# Zero-out
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
@ -320,7 +348,7 @@ class TestBatchPackagingReconcile(TransactionCase):
|
||||||
"""Lines with keep_qty=True are not zeroed out."""
|
"""Lines with keep_qty=True are not zeroed out."""
|
||||||
ineligible = self._make_product(self.uom_kg, 10.0)
|
ineligible = self._make_product(self.uom_kg, 10.0)
|
||||||
bp = self._add_lines(ineligible, [(23.0, 24.0)])
|
bp = self._add_lines(ineligible, [(23.0, 24.0)])
|
||||||
orig_qtys = {l.id: l.product_uom_qty for l in bp.sale_order_line_ids}
|
orig_qtys = {line.id: line.product_uom_qty for line in bp.sale_order_line_ids}
|
||||||
|
|
||||||
action = self.batch.action_fill_packages()
|
action = self.batch.action_fill_packages()
|
||||||
wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
|
wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,37 @@
|
||||||
<field name="model">sale.order.batch</field>
|
<field name="model">sale.order.batch</field>
|
||||||
<field name="inherit_id" ref="sale_order_batch.view_order_batch_form"/>
|
<field name="inherit_id" ref="sale_order_batch.view_order_batch_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
|
<!-- Header buttons -->
|
||||||
|
<xpath expr="//button[@id='action_confirm_primary']" position="before">
|
||||||
|
<field name="has_qty_adjusted" invisible="1"/>
|
||||||
|
<field name="has_fillable_packages" invisible="1"/>
|
||||||
|
<button
|
||||||
|
name="action_fill_packages"
|
||||||
|
type="object"
|
||||||
|
class="btn-primary"
|
||||||
|
string="Fill Packages"
|
||||||
|
attrs="{'invisible': ['|', ('state', 'not in', ['in_progress']), ('has_fillable_packages', '=', False)]}"
|
||||||
|
/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//button[@id='action_confirm_primary']" position="attributes">
|
||||||
|
<attribute
|
||||||
|
name="attrs"
|
||||||
|
>{'invisible': ['|', ('state', 'not in', ['in_progress']), ('has_fillable_packages', '=', True)]}</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//button[@id='action_confirm_secondary']" position="attributes">
|
||||||
|
<attribute
|
||||||
|
name="attrs"
|
||||||
|
>{"invisible": ["|", ["state", "not in", ["draft", "in_progress"]], ["has_fillable_packages", "=", False]]}</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//button[@id='create_invoices']" position="before">
|
||||||
|
<button
|
||||||
|
name="action_restore_ordered_qty"
|
||||||
|
type="object"
|
||||||
|
string="Restore Ordered Qty"
|
||||||
|
attrs="{'invisible': ['|', ('state', '!=', 'in_progress'),('has_qty_adjusted', '=', False)]}"
|
||||||
|
/>
|
||||||
|
</xpath>
|
||||||
|
<!-- Fields -->
|
||||||
<xpath
|
<xpath
|
||||||
expr="//field[@name='sale_order_line_ids']/tree/field[@name='product_uom_qty']"
|
expr="//field[@name='sale_order_line_ids']/tree/field[@name='product_uom_qty']"
|
||||||
position="before"
|
position="before"
|
||||||
|
|
@ -26,42 +57,6 @@
|
||||||
<xpath expr="//field[@name='product_ids']/tree/field[@name='product_id']" position="attributes">
|
<xpath expr="//field[@name='product_ids']/tree/field[@name='product_id']" position="attributes">
|
||||||
<attribute name="decoration-warning">(open_packaging_qty != 0)</attribute>
|
<attribute name="decoration-warning">(open_packaging_qty != 0)</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
<!-- Confirm (in_progress): primary when nothing to fill, secondary otherwise -->
|
|
||||||
<xpath expr="//button[@id='action_confirm'][@class='btn-primary']" position="replace">
|
|
||||||
<button
|
|
||||||
name="action_confirm"
|
|
||||||
id="action_confirm"
|
|
||||||
data-hotkey="v"
|
|
||||||
string="Confirm"
|
|
||||||
type="object"
|
|
||||||
class="btn-primary"
|
|
||||||
attrs="{'invisible': ['|', ('state', 'not in', ['in_progress']), ('has_fillable_packages', '=', True)]}"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
name="action_confirm"
|
|
||||||
string="Confirm"
|
|
||||||
type="object"
|
|
||||||
class="btn-secondary"
|
|
||||||
attrs="{'invisible': ['|', ('state', 'not in', ['in_progress']), ('has_fillable_packages', '=', False)]}"
|
|
||||||
/>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//header" position="inside">
|
|
||||||
<field name="has_qty_adjusted" invisible="1"/>
|
|
||||||
<field name="has_fillable_packages" invisible="1"/>
|
|
||||||
<button
|
|
||||||
name="action_fill_packages"
|
|
||||||
type="object"
|
|
||||||
class="btn-primary"
|
|
||||||
string="Fill Packages"
|
|
||||||
attrs="{'invisible': ['|', ('state', 'not in', ['in_progress']), ('has_fillable_packages', '=', False)]}"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
name="action_restore_ordered_qty"
|
|
||||||
type="object"
|
|
||||||
string="Restore Ordered Qty"
|
|
||||||
attrs="{'invisible': [('has_qty_adjusted', '=', False)]}"
|
|
||||||
/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,18 @@ class SaleOrderBatchPackagingReconcile(models.TransientModel):
|
||||||
zero_line_ids = fields.One2many(
|
zero_line_ids = fields.One2many(
|
||||||
"sale.order.batch.packaging.reconcile.zero.line", "wizard_id"
|
"sale.order.batch.packaging.reconcile.zero.line", "wizard_id"
|
||||||
)
|
)
|
||||||
|
has_fill_lines = fields.Boolean(compute="_compute_has_fill_lines")
|
||||||
|
has_zero_lines = fields.Boolean(compute="_compute_has_zero_lines")
|
||||||
|
|
||||||
|
@api.depends("line_ids")
|
||||||
|
def _compute_has_fill_lines(self):
|
||||||
|
for wizard in self:
|
||||||
|
wizard.has_fill_lines = bool(wizard.line_ids)
|
||||||
|
|
||||||
|
@api.depends("zero_line_ids")
|
||||||
|
def _compute_has_zero_lines(self):
|
||||||
|
for wizard in self:
|
||||||
|
wizard.has_zero_lines = bool(wizard.zero_line_ids)
|
||||||
|
|
||||||
def action_reconcile(self):
|
def action_reconcile(self):
|
||||||
for line in self.line_ids:
|
for line in self.line_ids:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@
|
||||||
<form string="Fill Packages">
|
<form string="Fill Packages">
|
||||||
<sheet>
|
<sheet>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Fill Packages" name="fill_packages">
|
<page
|
||||||
|
string="Fill Packages"
|
||||||
|
name="fill_packages"
|
||||||
|
attrs="{'invisible': [('has_fill_lines', '=', False)]}"
|
||||||
|
>
|
||||||
|
<field name="has_fill_lines" invisible="1"/>
|
||||||
<field name="line_ids">
|
<field name="line_ids">
|
||||||
<tree editable="bottom">
|
<tree editable="bottom">
|
||||||
<field name="product_id" readonly="1"/>
|
<field name="product_id" readonly="1"/>
|
||||||
|
|
@ -16,7 +21,8 @@
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
<page string="Zero Out" name="zero_out">
|
<page string="Zero Out" name="zero_out" attrs="{'invisible': [('has_zero_lines', '=', False)]}">
|
||||||
|
<field name="has_zero_lines" invisible="1"/>
|
||||||
<field name="zero_line_ids">
|
<field name="zero_line_ids">
|
||||||
<tree editable="bottom">
|
<tree editable="bottom">
|
||||||
<field name="product_id" readonly="1"/>
|
<field name="product_id" readonly="1"/>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue