Skip to content

Commit f54609b

Browse files
committed
[16.0][IMP] subscription_oca: Improve subscription lines view with section/note support and added filters
1 parent 7176669 commit f54609b

File tree

3 files changed

+277
-10
lines changed

3 files changed

+277
-10
lines changed

subscription_oca/models/sale_subscription_line.py

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,33 @@ class SaleSubscriptionLine(models.Model):
4040
readonly=False,
4141
)
4242

43-
@api.depends("product_id", "price_unit", "product_uom_qty", "discount", "tax_ids")
43+
sequence = fields.Integer(default=10, help="Display order on the subscription.")
44+
display_type = fields.Selection(
45+
selection=[("line_section", "Section"), ("line_note", "Note")],
46+
default=False,
47+
help="Technical field for UX purpose.",
48+
)
49+
50+
@api.depends(
51+
"product_id",
52+
"price_unit",
53+
"product_uom_qty",
54+
"discount",
55+
"tax_ids",
56+
"display_type",
57+
)
4458
def _compute_subtotal(self):
4559
for record in self:
60+
if record.display_type:
61+
record.update(
62+
{
63+
"amount_tax_line_amount": 0.0,
64+
"price_total": 0.0,
65+
"price_subtotal": 0.0,
66+
}
67+
)
68+
continue
69+
4670
price = record.price_unit * (1 - (record.discount or 0.0) / 100.0)
4771
taxes = record.tax_ids.compute_all(
4872
price,
@@ -80,9 +104,11 @@ def _compute_subtotal(self):
80104
index=True,
81105
)
82106

83-
@api.depends("product_id")
107+
@api.depends("product_id", "display_type")
84108
def _compute_name(self):
85109
for record in self:
110+
if record.display_type:
111+
continue
86112
if not record.product_id:
87113
record.name = False
88114
lang = get_lang(self.env, record.sale_subscription_id.partner_id.lang).code
@@ -91,9 +117,15 @@ def _compute_name(self):
91117
lang=lang
92118
).get_product_multiline_description_sale()
93119

94-
@api.depends("product_id", "sale_subscription_id.fiscal_position_id")
120+
@api.depends(
121+
"product_id", "display_type", "sale_subscription_id.fiscal_position_id"
122+
)
95123
def _compute_tax_ids(self):
96124
for line in self:
125+
if line.display_type:
126+
line.tax_ids = [(5, 0, 0)]
127+
continue
128+
97129
fpos = (
98130
line.sale_subscription_id.fiscal_position_id
99131
or line.sale_subscription_id.fiscal_position_id._get_fiscal_position(
@@ -110,9 +142,13 @@ def _compute_tax_ids(self):
110142
"product_id",
111143
"sale_subscription_id.partner_id",
112144
"sale_subscription_id.pricelist_id",
145+
"display_type",
113146
)
114147
def _compute_price_unit(self):
115148
for record in self:
149+
if record.display_type:
150+
record.price_unit = 0.0
151+
continue
116152
if not record.product_id:
117153
continue
118154
if (
@@ -143,9 +179,13 @@ def _compute_price_unit(self):
143179
"tax_ids",
144180
"sale_subscription_id.partner_id",
145181
"sale_subscription_id.pricelist_id",
182+
"display_type",
146183
)
147184
def _compute_discount(self):
148185
for record in self:
186+
if record.display_type:
187+
record.discount = 0.0
188+
continue
149189
if not (
150190
record.product_id
151191
and record.product_id.uom_id
@@ -289,6 +329,12 @@ def _get_display_price(self, product):
289329

290330
def _prepare_sale_order_line(self):
291331
self.ensure_one()
332+
if self.display_type:
333+
return {
334+
"display_type": self.display_type,
335+
"name": self.name or "",
336+
"sequence": self.sequence,
337+
}
292338
return {
293339
"product_id": self.product_id.id,
294340
"name": self.name,
@@ -298,10 +344,17 @@ def _prepare_sale_order_line(self):
298344
"price_subtotal": self.price_subtotal,
299345
"tax_id": self.tax_ids,
300346
"product_uom": self.product_id.uom_id.id,
347+
"sequence": self.sequence,
301348
}
302349

303350
def _prepare_account_move_line(self):
304351
self.ensure_one()
352+
if self.display_type:
353+
return {
354+
"display_type": self.display_type,
355+
"name": self.name or "",
356+
"sequence": self.sequence,
357+
}
305358
account = (
306359
self.product_id.property_account_income_id
307360
or self.product_id.categ_id.property_account_income_categ_id
@@ -316,4 +369,5 @@ def _prepare_account_move_line(self):
316369
"tax_ids": [(6, 0, self.tax_ids.ids)],
317370
"product_uom_id": self.product_id.uom_id.id,
318371
"account_id": account.id,
372+
"sequence": self.sequence,
319373
}

subscription_oca/tests/test_subscription_oca.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,3 +691,121 @@ def _collect_all_sub_test_results(self, subscription):
691691
)
692692
test_res.append(group_stage_ids)
693693
return test_res
694+
695+
def test_display_type_section_line_computes_and_prepares(self):
696+
# Selection line
697+
line = self.env["sale.subscription.line"].create(
698+
{
699+
"company_id": 1,
700+
"sale_subscription_id": self.sub1.id,
701+
"display_type": "line_section",
702+
"name": "Plans and prices",
703+
"sequence": 5,
704+
}
705+
)
706+
707+
# Comptes: must be all zero or empty
708+
self.assertEqual(line.price_unit, 0.0)
709+
self.assertEqual(line.discount, 0.0)
710+
self.assertEqual(line.price_subtotal, 0.0)
711+
self.assertEqual(line.price_total, 0.0)
712+
self.assertEqual(line.amount_tax_line_amount, 0.0)
713+
self.assertFalse(line.tax_ids, "tax_ids must be empty with display_type")
714+
715+
# The name should not be overwritten by the product (it keeps what we set)
716+
self.assertEqual(line.name, "Plans and prices")
717+
718+
# Prepare values: only display fields
719+
so_vals = line._prepare_sale_order_line()
720+
self.assertEqual(so_vals.get("display_type"), "line_section")
721+
self.assertEqual(so_vals.get("name"), "Plans and prices")
722+
self.assertEqual(so_vals.get("sequence"), 5)
723+
724+
# It should not include product/price keys
725+
self.assertNotIn("product_id", so_vals)
726+
self.assertNotIn("price_unit", so_vals)
727+
self.assertNotIn("discount", so_vals)
728+
self.assertNotIn("tax_id", so_vals)
729+
730+
aml_vals = line._prepare_account_move_line()
731+
self.assertEqual(aml_vals.get("display_type"), "line_section")
732+
self.assertEqual(aml_vals.get("name"), "Plans and prices")
733+
self.assertEqual(aml_vals.get("sequence"), 5)
734+
self.assertNotIn("product_id", aml_vals)
735+
self.assertNotIn("price_unit", aml_vals)
736+
self.assertNotIn("discount", aml_vals)
737+
self.assertNotIn("tax_ids", aml_vals)
738+
739+
def test_display_type_note_line_computes_and_prepares(self):
740+
# Note line
741+
line = self.env["sale.subscription.line"].create(
742+
{
743+
"company_id": 1,
744+
"sale_subscription_id": self.sub1.id,
745+
"display_type": "line_note",
746+
"name": "Note: discount applicable from the 2nd year",
747+
"sequence": 15,
748+
}
749+
)
750+
# Computes: zero / empty
751+
self.assertEqual(line.price_unit, 0.0)
752+
self.assertEqual(line.discount, 0.0)
753+
self.assertEqual(line.price_subtotal, 0.0)
754+
self.assertEqual(line.price_total, 0.0)
755+
self.assertEqual(line.amount_tax_line_amount, 0.0)
756+
self.assertFalse(line.tax_ids)
757+
758+
# Prepare values: only display fields
759+
so_vals = line._prepare_sale_order_line()
760+
self.assertEqual(so_vals.get("display_type"), "line_note")
761+
self.assertEqual(
762+
so_vals.get("name"), "Note: discount applicable from the 2nd year"
763+
)
764+
self.assertEqual(so_vals.get("sequence"), 15)
765+
self.assertNotIn("product_id", so_vals)
766+
self.assertNotIn("price_unit", so_vals)
767+
self.assertNotIn("discount", so_vals)
768+
self.assertNotIn("tax_id", so_vals)
769+
770+
aml_vals = line._prepare_account_move_line()
771+
self.assertEqual(aml_vals.get("display_type"), "line_note")
772+
self.assertEqual(
773+
aml_vals.get("name"), "Note: discount applicable from the 2nd year"
774+
)
775+
self.assertEqual(aml_vals.get("sequence"), 15)
776+
self.assertNotIn("product_id", aml_vals)
777+
self.assertNotIn("price_unit", aml_vals)
778+
self.assertNotIn("discount", aml_vals)
779+
self.assertNotIn("tax_ids", aml_vals)
780+
781+
def test_display_type_toggle_from_normal_line(self):
782+
# Start with a normal line (with product) so that there are imports > 0
783+
line = self.create_sub_line(self.sub1, self.product_1.id)
784+
self.assertGreater(line.price_subtotal, 0.0)
785+
self.assertTrue(line.tax_ids)
786+
787+
# Now we convert it to a display line (note or section)
788+
line.display_type = "line_note"
789+
# Computes must be all zero/empty
790+
self.assertEqual(line.price_unit, 0.0)
791+
self.assertEqual(line.discount, 0.0)
792+
self.assertEqual(line.price_subtotal, 0.0)
793+
self.assertEqual(line.price_total, 0.0)
794+
self.assertEqual(line.amount_tax_line_amount, 0.0)
795+
self.assertFalse(line.tax_ids)
796+
797+
# Prepare vals have to be display
798+
so_vals = line._prepare_sale_order_line()
799+
self.assertEqual(so_vals.get("display_type"), "line_note")
800+
self.assertIn("name", so_vals)
801+
self.assertNotIn("product_id", so_vals)
802+
self.assertNotIn("price_unit", so_vals)
803+
self.assertNotIn("discount", so_vals)
804+
805+
aml_vals = line._prepare_account_move_line()
806+
self.assertEqual(aml_vals.get("display_type"), "line_note")
807+
self.assertIn("name", aml_vals)
808+
self.assertNotIn("product_id", aml_vals)
809+
self.assertNotIn("price_unit", aml_vals)
810+
self.assertNotIn("discount", aml_vals)
811+
self.assertNotIn("tax_ids", aml_vals)

subscription_oca/views/sale_subscription_views.xml

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,19 +118,58 @@
118118
string="Subscription lines"
119119
name="subscription_lines_page"
120120
>
121-
<field name="sale_subscription_line_ids">
121+
<field
122+
name="sale_subscription_line_ids"
123+
widget="section_and_note_one2many"
124+
>
122125
<tree editable="bottom">
123-
<field name="product_id" required="True" />
126+
<control>
127+
<create string="Add a line" />
128+
<create
129+
string="Add a section"
130+
context="{'default_display_type': 'line_section'}"
131+
/>
132+
<create
133+
string="Add a note"
134+
context="{'default_display_type': 'line_note'}"
135+
/>
136+
</control>
137+
138+
<field name="display_type" invisible="1" />
139+
<field name="sequence" widget="handle" />
140+
141+
<field
142+
name="product_id"
143+
attrs="{'required': [('display_type','=',False)], 'readonly': [('display_type','!=',False)]}"
144+
/>
145+
124146
<field
125147
name="name"
126-
required="True"
127148
widget="section_and_note_text"
149+
attrs="{'required': [('display_type','=',False)]}"
128150
/>
151+
129152
<field name="currency_id" invisible="1" />
130-
<field name="product_uom_qty" required="True" />
131-
<field name="price_unit" required="True" />
132-
<field name="discount" required="True" />
133-
<field name="tax_ids" widget="many2many_tags" />
153+
154+
<field
155+
name="product_uom_qty"
156+
attrs="{'invisible': [('display_type','!=', False)]}"
157+
/>
158+
<field
159+
name="price_unit"
160+
attrs="{'invisible': [('display_type','!=', False)]}"
161+
/>
162+
<field
163+
name="discount"
164+
attrs="{'invisible': [('display_type','!=', False)]}"
165+
/>
166+
167+
<field
168+
name="tax_ids"
169+
widget="many2many_tags"
170+
attrs="{'invisible': [('display_type','!=', False)]}"
171+
/>
172+
134173
<field
135174
name="price_subtotal"
136175
options="{'currency_field': 'currency_id'}"
@@ -206,6 +245,43 @@
206245
</field>
207246
</record>
208247

248+
<record id="sale_subscription_line_form" model="ir.ui.view">
249+
<field name="name">sale.subscription.line.form</field>
250+
<field name="model">sale.subscription.line</field>
251+
<field name="arch" type="xml">
252+
<form>
253+
<sheet>
254+
<field name="display_type" invisible="1" />
255+
<group attrs="{'invisible': [('display_type','!=', False)]}">
256+
<field name="product_id" required="1" />
257+
<field name="product_uom_qty" />
258+
<field name="price_unit" />
259+
<field name="discount" />
260+
<field name="tax_ids" widget="many2many_tags" />
261+
</group>
262+
263+
<label
264+
for="name"
265+
string="Description"
266+
attrs="{'invisible': [('display_type','!=', False)]}"
267+
/>
268+
<label
269+
for="name"
270+
string="Section"
271+
attrs="{'invisible': [('display_type','!=','line_section')]}"
272+
/>
273+
<label
274+
for="name"
275+
string="Note"
276+
attrs="{'invisible': [('display_type','!=','line_note')]}"
277+
/>
278+
<field name="name" nolabel="1" />
279+
280+
</sheet>
281+
</form>
282+
</field>
283+
</record>
284+
209285
<record id="sale_subscription_tree" model="ir.ui.view">
210286
<field name="name">sale.subscription.tree</field>
211287
<field name="model">sale.subscription</field>
@@ -389,6 +465,25 @@
389465
string="Next invoice date"
390466
date="recurring_next_date"
391467
/>
468+
469+
<filter
470+
name="in_progress"
471+
string="In progress"
472+
domain="[('in_progress','=', True)]"
473+
/>
474+
475+
<filter
476+
name="expired"
477+
string="Expired"
478+
domain="[('active','=', True), ('date','!=', False), ('date','&lt;', context_today().strftime('%Y-%m-%d'))]"
479+
/>
480+
481+
<filter
482+
name="inactive"
483+
string="Archived"
484+
domain="[('active','=', False)]"
485+
/>
486+
392487
<group expand="0" string="Group By">
393488
<filter
394489
string="Partner"

0 commit comments

Comments
 (0)