Purchase & Sale Lines
This commit is contained in:
@@ -2297,6 +2297,78 @@ class Line(metaclass=PoolMeta):
|
||||
'get_premium', setter='set_premium')
|
||||
fee_ = fields.Many2One('fee.fee',"Fee")
|
||||
pricing_rule = fields.Text("Pricing description")
|
||||
parent_trader = fields.Function(
|
||||
fields.Many2One('party.party', "Trader"), 'get_parent_field')
|
||||
parent_operator = fields.Function(
|
||||
fields.Many2One('party.party', "Operator"), 'get_parent_field')
|
||||
parent_broker = fields.Function(
|
||||
fields.Many2One('party.party', "Broker"), 'get_parent_field')
|
||||
parent_contract_template = fields.Function(
|
||||
fields.Many2One('contract.template', "Contract Template"),
|
||||
'get_parent_field')
|
||||
parent_our_reference = fields.Function(
|
||||
fields.Char("Our Reference"), 'get_parent_field')
|
||||
parent_lc_date = fields.Function(
|
||||
fields.Date("LC date"), 'get_parent_field')
|
||||
parent_product_origin = fields.Function(
|
||||
fields.Char("Origin"), 'get_parent_field')
|
||||
price_component_count = fields.Function(
|
||||
fields.Integer("Components"), 'get_relation_count')
|
||||
pricing_count = fields.Function(
|
||||
fields.Integer("Pricing"), 'get_relation_count')
|
||||
price_summary_count = fields.Function(
|
||||
fields.Integer("Summary"), 'get_relation_count')
|
||||
premium_decomposition_count = fields.Function(
|
||||
fields.Integer("Premium lines"), 'get_relation_count')
|
||||
mtm_count = fields.Function(
|
||||
fields.Integer("MTM"), 'get_relation_count')
|
||||
fee_count = fields.Function(
|
||||
fields.Integer("Fees"), 'get_relation_count')
|
||||
lot_count = fields.Function(
|
||||
fields.Integer("Lots"), 'get_relation_count')
|
||||
derivative_count = fields.Function(
|
||||
fields.Integer("Derivatives"), 'get_relation_count')
|
||||
estimated_date_count = fields.Function(
|
||||
fields.Integer("Estimated dates"), 'get_relation_count')
|
||||
charter_condition_count = fields.Function(
|
||||
fields.Integer("Charter conditions"), 'get_relation_count')
|
||||
|
||||
_parent_field_map = {
|
||||
'parent_trader': 'trader',
|
||||
'parent_operator': 'operator',
|
||||
'parent_broker': 'broker',
|
||||
'parent_contract_template': 'contract_template',
|
||||
'parent_our_reference': 'our_reference',
|
||||
'parent_lc_date': 'lc_date',
|
||||
'parent_product_origin': 'product_origin',
|
||||
}
|
||||
_relation_count_map = {
|
||||
'price_component_count': 'price_components',
|
||||
'pricing_count': 'price_pricing',
|
||||
'price_summary_count': 'price_summary',
|
||||
'premium_decomposition_count': 'premium_decomposition',
|
||||
'mtm_count': 'mtm',
|
||||
'fee_count': 'fees',
|
||||
'lot_count': 'lots',
|
||||
'derivative_count': 'derivatives',
|
||||
'estimated_date_count': 'estimated_date',
|
||||
'charter_condition_count': 'charter_conditions',
|
||||
}
|
||||
|
||||
def get_parent_field(self, name=None):
|
||||
field_name = self._parent_field_map.get(name)
|
||||
if not field_name or not self.purchase:
|
||||
return None
|
||||
value = getattr(self.purchase, field_name, None)
|
||||
if hasattr(value, 'id'):
|
||||
return value.id
|
||||
return value
|
||||
|
||||
def get_relation_count(self, name=None):
|
||||
field_name = self._relation_count_map.get(name)
|
||||
if not field_name:
|
||||
return 0
|
||||
return len(getattr(self, field_name, None) or [])
|
||||
|
||||
@classmethod
|
||||
def default_unit(cls):
|
||||
|
||||
@@ -37,11 +37,31 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="inherit" ref="purchase.purchase_line_view_form"/>
|
||||
<field name="name">purchase_line_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="purchase_line_view_tree_sequence">
|
||||
<field name="model">purchase.line</field>
|
||||
<field name="inherit" ref="purchase.purchase_line_view_tree_sequence"/>
|
||||
<field name="name">purchase_line_tree_sequence</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="purchase_line_view_tree_sequence">
|
||||
<field name="model">purchase.line</field>
|
||||
<field name="inherit" ref="purchase.purchase_line_view_tree_sequence"/>
|
||||
<field name="name">purchase_line_tree_sequence</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="purchase_line_reporting_view_tree">
|
||||
<field name="model">purchase.line</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">purchase_line_reporting_tree</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_purchase_line_reporting">
|
||||
<field name="name">Purchase Lines</field>
|
||||
<field name="res_model">purchase.line</field>
|
||||
<field name="domain" eval="[('type', '=', 'line')]" pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_purchase_line_reporting_view_tree">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="purchase_line_reporting_view_tree"/>
|
||||
<field name="act_window" ref="act_purchase_line_reporting"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_purchase_line_reporting_view_form">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="purchase_line_view_form"/>
|
||||
<field name="act_window" ref="act_purchase_line_reporting"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="act_bi">
|
||||
<field name="name">🧠 Go to BI</field>
|
||||
@@ -406,5 +426,11 @@ this repository contains the full copyright notices and license terms. -->
|
||||
sequence="16"
|
||||
action="act_contract_template_form"
|
||||
id="menu_purchase_contract_template_form"/>
|
||||
<menuitem
|
||||
name="Purchase Lines"
|
||||
parent="purchase.menu_purchase"
|
||||
sequence="17"
|
||||
action="act_purchase_line_reporting"
|
||||
id="menu_purchase_line_reporting"/>
|
||||
</data>
|
||||
</tryton>
|
||||
|
||||
@@ -2144,6 +2144,87 @@ class SaleLine(metaclass=PoolMeta):
|
||||
finished = fields.Boolean("Mark as finished")
|
||||
pricing_rule = fields.Text("Pricing description")
|
||||
price_composition = fields.One2Many('price.composition','sale_line',"Price composition")
|
||||
parent_trader = fields.Function(
|
||||
fields.Many2One('party.party', "Trader"), 'get_parent_field')
|
||||
parent_operator = fields.Function(
|
||||
fields.Many2One('party.party', "Operator"), 'get_parent_field')
|
||||
parent_broker = fields.Function(
|
||||
fields.Many2One('party.party', "Broker"), 'get_parent_field')
|
||||
parent_contract_template = fields.Function(
|
||||
fields.Many2One('contract.template', "Contract Template"),
|
||||
'get_parent_field')
|
||||
parent_invoice_party = fields.Function(
|
||||
fields.Many2One('party.party', "Invoice Party"), 'get_parent_field')
|
||||
parent_shipment_party = fields.Function(
|
||||
fields.Many2One('party.party', "Shipment Party"), 'get_parent_field')
|
||||
parent_our_reference = fields.Function(
|
||||
fields.Char("Our Reference"), 'get_parent_field')
|
||||
parent_lc_date = fields.Function(
|
||||
fields.Date("LC date"), 'get_parent_field')
|
||||
parent_product_origin = fields.Function(
|
||||
fields.Char("Origin"), 'get_parent_field')
|
||||
price_component_count = fields.Function(
|
||||
fields.Integer("Components"), 'get_relation_count')
|
||||
pricing_count = fields.Function(
|
||||
fields.Integer("Pricing"), 'get_relation_count')
|
||||
price_summary_count = fields.Function(
|
||||
fields.Integer("Summary"), 'get_relation_count')
|
||||
price_composition_count = fields.Function(
|
||||
fields.Integer("Price composition"), 'get_relation_count')
|
||||
premium_decomposition_count = fields.Function(
|
||||
fields.Integer("Premium lines"), 'get_relation_count')
|
||||
mtm_count = fields.Function(
|
||||
fields.Integer("MTM"), 'get_relation_count')
|
||||
fee_count = fields.Function(
|
||||
fields.Integer("Fees"), 'get_relation_count')
|
||||
lot_count = fields.Function(
|
||||
fields.Integer("Lots"), 'get_relation_count')
|
||||
derivative_count = fields.Function(
|
||||
fields.Integer("Derivatives"), 'get_relation_count')
|
||||
estimated_date_count = fields.Function(
|
||||
fields.Integer("Estimated dates"), 'get_relation_count')
|
||||
charter_condition_count = fields.Function(
|
||||
fields.Integer("Charter conditions"), 'get_relation_count')
|
||||
|
||||
_parent_field_map = {
|
||||
'parent_trader': 'trader',
|
||||
'parent_operator': 'operator',
|
||||
'parent_broker': 'broker',
|
||||
'parent_contract_template': 'contract_template',
|
||||
'parent_invoice_party': 'invoice_party',
|
||||
'parent_shipment_party': 'shipment_party',
|
||||
'parent_our_reference': 'our_reference',
|
||||
'parent_lc_date': 'lc_date',
|
||||
'parent_product_origin': 'product_origin',
|
||||
}
|
||||
_relation_count_map = {
|
||||
'price_component_count': 'price_components',
|
||||
'pricing_count': 'price_pricing',
|
||||
'price_summary_count': 'price_summary',
|
||||
'price_composition_count': 'price_composition',
|
||||
'premium_decomposition_count': 'premium_decomposition',
|
||||
'mtm_count': 'mtm',
|
||||
'fee_count': 'fees',
|
||||
'lot_count': 'lots',
|
||||
'derivative_count': 'derivatives',
|
||||
'estimated_date_count': 'estimated_date',
|
||||
'charter_condition_count': 'charter_conditions',
|
||||
}
|
||||
|
||||
def get_parent_field(self, name=None):
|
||||
field_name = self._parent_field_map.get(name)
|
||||
if not field_name or not self.sale:
|
||||
return None
|
||||
value = getattr(self.sale, field_name, None)
|
||||
if hasattr(value, 'id'):
|
||||
return value.id
|
||||
return value
|
||||
|
||||
def get_relation_count(self, name=None):
|
||||
field_name = self._relation_count_map.get(name)
|
||||
if not field_name:
|
||||
return 0
|
||||
return len(getattr(self, field_name, None) or [])
|
||||
|
||||
@classmethod
|
||||
def default_finished(cls):
|
||||
|
||||
@@ -13,11 +13,31 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="inherit" ref="sale.sale_line_view_form"/>
|
||||
<field name="name">sale_line_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="sale_line_view_tree_sequence">
|
||||
<field name="model">sale.line</field>
|
||||
<field name="inherit" ref="sale.sale_line_view_tree_sequence"/>
|
||||
<field name="name">sale_line_tree_sequence</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="sale_line_view_tree_sequence">
|
||||
<field name="model">sale.line</field>
|
||||
<field name="inherit" ref="sale.sale_line_view_tree_sequence"/>
|
||||
<field name="name">sale_line_tree_sequence</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="sale_line_reporting_view_tree">
|
||||
<field name="model">sale.line</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">sale_line_reporting_tree</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_sale_line_reporting">
|
||||
<field name="name">Sale Lines</field>
|
||||
<field name="res_model">sale.line</field>
|
||||
<field name="domain" eval="[('type', '=', 'line')]" pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_sale_line_reporting_view_tree">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="sale_line_reporting_view_tree"/>
|
||||
<field name="act_window" ref="act_sale_line_reporting"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_sale_line_reporting_view_form">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="sale_line_view_form"/>
|
||||
<field name="act_window" ref="act_sale_line_reporting"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="sale_lot_view_tree_sequence">
|
||||
<field name="model">lot.lot</field>
|
||||
@@ -63,10 +83,16 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="action" ref="act_sale_allocations_wizard"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="sale_btb_view_form">
|
||||
<field name="model">sale.sale</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">sale_btb_form</field>
|
||||
</record>
|
||||
</data>
|
||||
<record model="ir.ui.view" id="sale_btb_view_form">
|
||||
<field name="model">sale.sale</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">sale_btb_form</field>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Sale Lines"
|
||||
parent="sale.menu_sale"
|
||||
sequence="17"
|
||||
action="act_sale_line_reporting"
|
||||
id="menu_sale_line_reporting"/>
|
||||
</data>
|
||||
</tryton>
|
||||
|
||||
@@ -4161,6 +4161,66 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
'estimated_date': datetime.date(2026, 5, 1),
|
||||
}])])
|
||||
|
||||
def test_purchase_line_reporting_parent_fields_and_counts(self):
|
||||
'purchase line reporting fields mirror parent values and relation counts'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
line = PurchaseLine()
|
||||
line.purchase = Mock(
|
||||
trader=Mock(id=10),
|
||||
operator=Mock(id=11),
|
||||
broker=Mock(id=12),
|
||||
contract_template=Mock(id=13),
|
||||
our_reference='OUR-PUR',
|
||||
lc_date=datetime.date(2026, 5, 2),
|
||||
product_origin='Origin',
|
||||
)
|
||||
line.price_components = [Mock(), Mock()]
|
||||
line.price_pricing = [Mock()]
|
||||
line.mtm = [Mock(), Mock(), Mock()]
|
||||
line.lots = []
|
||||
|
||||
self.assertEqual(line.get_parent_field('parent_trader'), 10)
|
||||
self.assertEqual(line.get_parent_field('parent_operator'), 11)
|
||||
self.assertEqual(
|
||||
line.get_parent_field('parent_contract_template'), 13)
|
||||
self.assertEqual(line.get_parent_field('parent_our_reference'), 'OUR-PUR')
|
||||
self.assertEqual(
|
||||
line.get_relation_count('price_component_count'), 2)
|
||||
self.assertEqual(line.get_relation_count('pricing_count'), 1)
|
||||
self.assertEqual(line.get_relation_count('mtm_count'), 3)
|
||||
self.assertEqual(line.get_relation_count('lot_count'), 0)
|
||||
|
||||
def test_sale_line_reporting_parent_fields_and_counts(self):
|
||||
'sale line reporting fields mirror parent values and relation counts'
|
||||
SaleLine = Pool().get('sale.line')
|
||||
line = SaleLine()
|
||||
line.sale = Mock(
|
||||
trader=Mock(id=20),
|
||||
operator=Mock(id=21),
|
||||
invoice_party=Mock(id=22),
|
||||
shipment_party=Mock(id=23),
|
||||
contract_template=Mock(id=24),
|
||||
our_reference='OUR-SALE',
|
||||
lc_date=datetime.date(2026, 5, 3),
|
||||
product_origin='Origin',
|
||||
)
|
||||
line.price_components = [Mock()]
|
||||
line.price_composition = [Mock(), Mock()]
|
||||
line.price_pricing = []
|
||||
line.fees = [Mock(), Mock(), Mock()]
|
||||
|
||||
self.assertEqual(line.get_parent_field('parent_trader'), 20)
|
||||
self.assertEqual(line.get_parent_field('parent_operator'), 21)
|
||||
self.assertEqual(line.get_parent_field('parent_invoice_party'), 22)
|
||||
self.assertEqual(line.get_parent_field('parent_shipment_party'), 23)
|
||||
self.assertEqual(line.get_parent_field('parent_our_reference'), 'OUR-SALE')
|
||||
self.assertEqual(
|
||||
line.get_relation_count('price_component_count'), 1)
|
||||
self.assertEqual(
|
||||
line.get_relation_count('price_composition_count'), 2)
|
||||
self.assertEqual(line.get_relation_count('pricing_count'), 0)
|
||||
self.assertEqual(line.get_relation_count('fee_count'), 3)
|
||||
|
||||
def test_purchase_and_sale_line_keep_explicit_estimated_bl_date(self):
|
||||
'new lines keep explicit estimated BL date'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
|
||||
55
modules/purchase_trade/view/purchase_line_reporting_tree.xml
Normal file
55
modules/purchase_trade/view/purchase_line_reporting_tree.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="purchase" expand="1"/>
|
||||
<field name="purchase_state" widget="badge"
|
||||
badge_colors="draft:#64748b,quotation:#2563eb,confirmed:#16a34a,processing:#f59e0b,done:#0f766e,cancelled:#ef4444,*:#94a3b8"/>
|
||||
<field name="purchase_date"/>
|
||||
<field name="supplier" expand="1"/>
|
||||
<field name="parent_trader" optional="1"/>
|
||||
<field name="parent_operator" optional="1"/>
|
||||
<field name="parent_broker" optional="1"/>
|
||||
<field name="parent_our_reference" optional="1"/>
|
||||
<field name="parent_contract_template" optional="1"/>
|
||||
<field name="parent_lc_date" optional="1"/>
|
||||
<field name="product" expand="1"/>
|
||||
<field name="product_supplier" optional="1"/>
|
||||
<field name="attributes_name" optional="1"/>
|
||||
<field name="parent_product_origin" optional="1"/>
|
||||
<field name="del_period"/>
|
||||
<field name="from_del"/>
|
||||
<field name="to_del"/>
|
||||
<field name="period_at" optional="1"/>
|
||||
<field name="from_location" optional="1"/>
|
||||
<field name="to_location" optional="1"/>
|
||||
<field name="quantity_theorical" symbol="unit"/>
|
||||
<field name="quantity" symbol="unit"/>
|
||||
<field name="actual_quantity" symbol="unit" optional="1"/>
|
||||
<field name="targeted_qt" symbol="unit" optional="1"/>
|
||||
<field name="unit" optional="1"/>
|
||||
<field name="price_type" widget="badge"
|
||||
badge_colors="priced:#16a34a,basis:#2563eb,efp:#8b5cf6,cash:#f59e0b,*:#94a3b8"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="premium" optional="1"/>
|
||||
<field name="amount"/>
|
||||
<field name="currency"/>
|
||||
<field name="progress" widget="progressbar" optional="1"/>
|
||||
<field name="moves_progress" string="Shipping" widget="progressbar" optional="1"/>
|
||||
<field name="invoice_progress" string="Invoicing" widget="progressbar" optional="1"/>
|
||||
<field name="enable_linked_currency" optional="1"/>
|
||||
<field name="linked_price" optional="1"/>
|
||||
<field name="linked_currency" optional="1"/>
|
||||
<field name="linked_unit" optional="1"/>
|
||||
<field name="price_component_count" optional="1"/>
|
||||
<field name="pricing_count" optional="1"/>
|
||||
<field name="price_summary_count" optional="1"/>
|
||||
<field name="premium_decomposition_count" optional="1"/>
|
||||
<field name="mtm_count" optional="1"/>
|
||||
<field name="fee_count" optional="1"/>
|
||||
<field name="lot_count" optional="1"/>
|
||||
<field name="derivative_count" optional="1"/>
|
||||
<field name="estimated_date_count" optional="1"/>
|
||||
<field name="charter_condition_count" optional="1"/>
|
||||
<field name="pricing_rule" optional="1"/>
|
||||
<field name="certif" optional="1"/>
|
||||
<field name="finished" optional="1"/>
|
||||
</tree>
|
||||
58
modules/purchase_trade/view/sale_line_reporting_tree.xml
Normal file
58
modules/purchase_trade/view/sale_line_reporting_tree.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="sale" expand="1"/>
|
||||
<field name="sale_state" widget="badge"
|
||||
badge_colors="draft:#64748b,quotation:#2563eb,confirmed:#16a34a,processing:#f59e0b,done:#0f766e,cancelled:#ef4444,*:#94a3b8"/>
|
||||
<field name="sale_date"/>
|
||||
<field name="customer" expand="1"/>
|
||||
<field name="parent_invoice_party" optional="1"/>
|
||||
<field name="parent_shipment_party" optional="1"/>
|
||||
<field name="parent_trader" optional="1"/>
|
||||
<field name="parent_operator" optional="1"/>
|
||||
<field name="parent_broker" optional="1"/>
|
||||
<field name="parent_our_reference" optional="1"/>
|
||||
<field name="parent_contract_template" optional="1"/>
|
||||
<field name="parent_lc_date" optional="1"/>
|
||||
<field name="product" expand="1"/>
|
||||
<field name="attributes_name" optional="1"/>
|
||||
<field name="parent_product_origin" optional="1"/>
|
||||
<field name="del_period"/>
|
||||
<field name="from_del"/>
|
||||
<field name="to_del"/>
|
||||
<field name="period_at" optional="1"/>
|
||||
<field name="shipping_date" optional="1"/>
|
||||
<field name="from_location" optional="1"/>
|
||||
<field name="to_location" optional="1"/>
|
||||
<field name="quantity_theorical" symbol="unit"/>
|
||||
<field name="quantity" symbol="unit"/>
|
||||
<field name="actual_quantity" symbol="unit" optional="1"/>
|
||||
<field name="targeted_qt" symbol="unit" optional="1"/>
|
||||
<field name="unit" optional="1"/>
|
||||
<field name="price_type" widget="badge"
|
||||
badge_colors="priced:#16a34a,basis:#2563eb,efp:#8b5cf6,cash:#f59e0b,*:#94a3b8"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="premium" optional="1"/>
|
||||
<field name="amount"/>
|
||||
<field name="currency"/>
|
||||
<field name="progress" widget="progressbar" optional="1"/>
|
||||
<field name="moves_progress" string="Shipping" widget="progressbar" optional="1"/>
|
||||
<field name="invoice_progress" string="Invoicing" widget="progressbar" optional="1"/>
|
||||
<field name="enable_linked_currency" optional="1"/>
|
||||
<field name="linked_price" optional="1"/>
|
||||
<field name="linked_currency" optional="1"/>
|
||||
<field name="linked_unit" optional="1"/>
|
||||
<field name="price_component_count" optional="1"/>
|
||||
<field name="pricing_count" optional="1"/>
|
||||
<field name="price_summary_count" optional="1"/>
|
||||
<field name="price_composition_count" optional="1"/>
|
||||
<field name="premium_decomposition_count" optional="1"/>
|
||||
<field name="mtm_count" optional="1"/>
|
||||
<field name="fee_count" optional="1"/>
|
||||
<field name="lot_count" optional="1"/>
|
||||
<field name="derivative_count" optional="1"/>
|
||||
<field name="estimated_date_count" optional="1"/>
|
||||
<field name="charter_condition_count" optional="1"/>
|
||||
<field name="pricing_rule" optional="1"/>
|
||||
<field name="certif" optional="1"/>
|
||||
<field name="finished" optional="1"/>
|
||||
</tree>
|
||||
Reference in New Issue
Block a user