odoo-addon-sale-blanket-order 16.0.2.0.0.2__py3-none-any.whl → 18.0.1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. odoo/addons/sale_blanket_order/README.rst +111 -63
  2. odoo/addons/sale_blanket_order/__manifest__.py +8 -3
  3. odoo/addons/sale_blanket_order/data/ir_cron.xml +0 -2
  4. odoo/addons/sale_blanket_order/i18n/de.po +65 -80
  5. odoo/addons/sale_blanket_order/i18n/es.po +30 -59
  6. odoo/addons/sale_blanket_order/i18n/fr.po +30 -59
  7. odoo/addons/sale_blanket_order/i18n/fr_FR.po +30 -59
  8. odoo/addons/sale_blanket_order/i18n/it.po +68 -88
  9. odoo/addons/sale_blanket_order/i18n/pt.po +15 -59
  10. odoo/addons/sale_blanket_order/i18n/sale_blanket_order.pot +16 -60
  11. odoo/addons/sale_blanket_order/models/blanket_orders.py +77 -143
  12. odoo/addons/sale_blanket_order/models/sale_config_settings.py +0 -1
  13. odoo/addons/sale_blanket_order/models/sale_orders.py +8 -14
  14. odoo/addons/sale_blanket_order/readme/CONTEXT.md +5 -0
  15. odoo/addons/sale_blanket_order/readme/CONTRIBUTORS.md +17 -0
  16. odoo/addons/sale_blanket_order/readme/{CREDITS.rst → CREDITS.md} +2 -1
  17. odoo/addons/sale_blanket_order/readme/DESCRIPTION.md +5 -0
  18. odoo/addons/sale_blanket_order/readme/ROADMAP.md +3 -0
  19. odoo/addons/sale_blanket_order/readme/USAGE.md +58 -0
  20. odoo/addons/sale_blanket_order/report/report.xml +0 -2
  21. odoo/addons/sale_blanket_order/report/templates.xml +32 -35
  22. odoo/addons/sale_blanket_order/static/description/index.html +87 -62
  23. odoo/addons/sale_blanket_order/static/src/js/disable_add_order_line.esm.js +22 -0
  24. odoo/addons/sale_blanket_order/tests/test_blanket_orders.py +99 -50
  25. odoo/addons/sale_blanket_order/tests/test_sale_order.py +7 -80
  26. odoo/addons/sale_blanket_order/views/sale_blanket_order_line_views.xml +7 -16
  27. odoo/addons/sale_blanket_order/views/sale_blanket_order_views.xml +31 -57
  28. odoo/addons/sale_blanket_order/views/sale_config_settings.xml +11 -19
  29. odoo/addons/sale_blanket_order/views/sale_order_views.xml +5 -8
  30. odoo/addons/sale_blanket_order/wizard/create_sale_orders.py +28 -26
  31. odoo/addons/sale_blanket_order/wizard/create_sale_orders.xml +12 -2
  32. odoo_addon_sale_blanket_order-18.0.1.2.1.dist-info/METADATA +216 -0
  33. odoo_addon_sale_blanket_order-18.0.1.2.1.dist-info/RECORD +49 -0
  34. {odoo_addon_sale_blanket_order-16.0.2.0.0.2.dist-info → odoo_addon_sale_blanket_order-18.0.1.2.1.dist-info}/WHEEL +1 -1
  35. odoo_addon_sale_blanket_order-18.0.1.2.1.dist-info/top_level.txt +1 -0
  36. odoo/addons/sale_blanket_order/readme/CONTRIBUTORS.rst +0 -8
  37. odoo/addons/sale_blanket_order/readme/DESCRIPTION.rst +0 -4
  38. odoo/addons/sale_blanket_order/readme/USAGE.rst +0 -53
  39. odoo_addon_sale_blanket_order-16.0.2.0.0.2.dist-info/METADATA +0 -168
  40. odoo_addon_sale_blanket_order-16.0.2.0.0.2.dist-info/RECORD +0 -46
  41. odoo_addon_sale_blanket_order-16.0.2.0.0.2.dist-info/top_level.txt +0 -1
@@ -1,13 +1,11 @@
1
1
  # Copyright 2018 ACSONE SA/NV
2
2
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3
3
 
4
- from odoo import SUPERUSER_ID, _, api, fields, models
4
+ from odoo import api, fields, models
5
5
  from odoo.exceptions import UserError
6
6
  from odoo.tools import float_is_zero
7
7
  from odoo.tools.misc import format_date
8
8
 
9
- from odoo.addons.sale.models.sale_order import READONLY_FIELD_STATES
10
-
11
9
 
12
10
  class BlanketOrder(models.Model):
13
11
  _name = "sale.blanket.order"
@@ -44,7 +42,6 @@ class BlanketOrder(models.Model):
44
42
  partner_id = fields.Many2one(
45
43
  "res.partner",
46
44
  string="Partner",
47
- states=READONLY_FIELD_STATES,
48
45
  )
49
46
  line_ids = fields.One2many(
50
47
  "sale.blanket.order.line", "order_id", string="Order lines", copy=True
@@ -63,21 +60,11 @@ class BlanketOrder(models.Model):
63
60
  "product.pricelist",
64
61
  string="Pricelist",
65
62
  required=True,
66
- states=READONLY_FIELD_STATES,
67
63
  )
68
64
  currency_id = fields.Many2one("res.currency", related="pricelist_id.currency_id")
69
- analytic_account_id = fields.Many2one(
70
- comodel_name="account.analytic.account",
71
- string="Analytic Account",
72
- copy=False,
73
- check_company=True,
74
- states=READONLY_FIELD_STATES,
75
- domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
76
- )
77
65
  payment_term_id = fields.Many2one(
78
66
  "account.payment.term",
79
67
  string="Payment Terms",
80
- states=READONLY_FIELD_STATES,
81
68
  )
82
69
  confirmed = fields.Boolean(copy=False)
83
70
  state = fields.Selection(
@@ -91,26 +78,21 @@ class BlanketOrder(models.Model):
91
78
  store=True,
92
79
  copy=False,
93
80
  )
94
- validity_date = fields.Date(
95
- states=READONLY_FIELD_STATES,
96
- )
81
+ validity_date = fields.Date()
97
82
  client_order_ref = fields.Char(
98
83
  string="Customer Reference",
99
84
  copy=False,
100
- states=READONLY_FIELD_STATES,
101
85
  )
102
- note = fields.Text(default=_default_note, states=READONLY_FIELD_STATES)
86
+ note = fields.Text(default=_default_note)
103
87
  user_id = fields.Many2one(
104
88
  "res.users",
105
89
  string="Salesperson",
106
- states=READONLY_FIELD_STATES,
107
90
  )
108
91
  team_id = fields.Many2one(
109
92
  "crm.team",
110
93
  string="Sales Team",
111
94
  change_default=True,
112
95
  default=lambda self: self.env["crm.team"]._get_default_team_id(),
113
- states=READONLY_FIELD_STATES,
114
96
  )
115
97
  tag_ids = fields.Many2many(
116
98
  comodel_name="crm.tag",
@@ -128,7 +110,9 @@ class BlanketOrder(models.Model):
128
110
  sale_count = fields.Integer(compute="_compute_sale_count")
129
111
 
130
112
  fiscal_position_id = fields.Many2one(
131
- "account.fiscal.position", string="Fiscal Position"
113
+ "account.fiscal.position",
114
+ string="Fiscal Position",
115
+ check_company=True,
132
116
  )
133
117
 
134
118
  amount_untaxed = fields.Monetary(
@@ -205,7 +189,7 @@ class BlanketOrder(models.Model):
205
189
  order.state = "expired"
206
190
  elif float_is_zero(
207
191
  sum(
208
- order.line_ids.filtered(lambda l: not l.display_type).mapped(
192
+ order.line_ids.filtered(lambda line: not line.display_type).mapped(
209
193
  "remaining_uom_qty"
210
194
  )
211
195
  ),
@@ -254,15 +238,15 @@ class BlanketOrder(models.Model):
254
238
 
255
239
  if self.partner_id.user_id:
256
240
  values["user_id"] = self.partner_id.user_id.id
257
- if self.partner_id.team_id:
258
- values["team_id"] = self.partner_id.team_id.id
241
+ if self.partner_id.user_id.sale_team_id:
242
+ values["team_id"] = self.partner_id.user_id.sale_team_id.id
259
243
  self.update(values)
260
244
 
261
245
  def unlink(self):
262
246
  for order in self:
263
247
  if order.state not in ("draft", "expired") or order._check_active_orders():
264
248
  raise UserError(
265
- _(
249
+ self.env._(
266
250
  "You can not delete an open blanket or "
267
251
  "with active sale orders! "
268
252
  "Try to cancel it before."
@@ -274,12 +258,12 @@ class BlanketOrder(models.Model):
274
258
  try:
275
259
  today = fields.Date.today()
276
260
  for order in self:
277
- assert order.validity_date, _("Validity date is mandatory")
278
- assert order.validity_date > today, _(
261
+ assert order.validity_date, self.env._("Validity date is mandatory")
262
+ assert order.validity_date > today, self.env._(
279
263
  "Validity date must be in the future"
280
264
  )
281
- assert order.partner_id, _("Partner is mandatory")
282
- assert len(order.line_ids) > 0, _("Must have some lines")
265
+ assert order.partner_id, self.env._("Partner is mandatory")
266
+ assert len(order.line_ids) > 0, self.env._("Must have some lines")
283
267
  order.line_ids._validate()
284
268
  except AssertionError as e:
285
269
  raise UserError(e) from e
@@ -310,7 +294,7 @@ class BlanketOrder(models.Model):
310
294
  for order in self:
311
295
  if order._check_active_orders():
312
296
  raise UserError(
313
- _(
297
+ self.env._(
314
298
  "You can not delete a blanket order with opened "
315
299
  "sale orders! "
316
300
  "Try to cancel them before."
@@ -437,9 +421,9 @@ class BlanketOrderLine(models.Model):
437
421
  product_uom = fields.Many2one("uom.uom", string="Unit of Measure")
438
422
  price_unit = fields.Float(string="Price", digits="Product Price")
439
423
  taxes_id = fields.Many2many(
440
- "account.tax",
441
- string="Taxes",
442
- domain=["|", ("active", "=", False), ("active", "=", True)],
424
+ comodel_name="account.tax",
425
+ context={"active_test": False},
426
+ check_company=True,
443
427
  )
444
428
  date_schedule = fields.Date(string="Scheduled Date")
445
429
  original_uom_qty = fields.Float(
@@ -490,115 +474,53 @@ class BlanketOrderLine(models.Model):
490
474
  default=False,
491
475
  help="Technical field for UX purpose.",
492
476
  )
477
+ pricelist_item_id = fields.Many2one(
478
+ comodel_name="product.pricelist.item", compute="_compute_pricelist_item_id"
479
+ )
493
480
 
494
- def name_get(self):
495
- result = []
481
+ @api.depends(
482
+ "order_id.name", "date_schedule", "remaining_uom_qty", "product_uom.name"
483
+ )
484
+ @api.depends_context("from_sale_order")
485
+ def _compute_display_name(self):
496
486
  if self.env.context.get("from_sale_order"):
497
487
  for record in self:
498
- res = "[%s]" % record.order_id.name
488
+ name = f"[{record.order_id.name}]"
499
489
  if record.date_schedule:
500
490
  formatted_date = format_date(record.env, record.date_schedule)
501
- res += " - {}: {}".format(_("Date Scheduled"), formatted_date)
502
- res += " ({}: {} {})".format(
503
- _("remaining"),
491
+ name += " - {}: {}".format(
492
+ self.env._("Date Scheduled"), formatted_date
493
+ )
494
+ name += " ({}: {} {})".format(
495
+ self.env._("remaining"),
504
496
  record.remaining_uom_qty,
505
497
  record.product_uom.name,
506
498
  )
507
- result.append((record.id, res))
508
- return result
509
- return super().name_get()
510
-
511
- def _get_real_price_currency(self, product, rule_id, qty, uom, pricelist_id):
512
- """Retrieve the price before applying the pricelist
513
- :param obj product: object of current product record
514
- :param float qty: total quentity of product
515
- :param tuple price_and_rule: tuple(price, suitable_rule) coming
516
- from pricelist computation
517
- :param obj uom: unit of measure of current order line
518
- :param integer pricelist_id: pricelist id of sale order"""
519
- # Copied and adapted from the sale module
520
- PricelistItem = self.env["product.pricelist.item"]
521
- field_name = "lst_price"
522
- currency_id = None
523
- product_currency = None
524
- if rule_id:
525
- pricelist_item = PricelistItem.browse(rule_id)
526
- if pricelist_item.pricelist_id.discount_policy == "without_discount":
527
- while (
528
- pricelist_item.base == "pricelist"
529
- and pricelist_item.base_pricelist_id
530
- and pricelist_item.base_pricelist_id.discount_policy
531
- == "without_discount"
532
- ):
533
- price, rule_id = pricelist_item.base_pricelist_id.with_context(
534
- uom=uom.id
535
- )._get_product_price_rule(product, qty, uom)
536
- pricelist_item = PricelistItem.browse(rule_id)
537
-
538
- if pricelist_item.base == "standard_price":
539
- field_name = "standard_price"
540
- if pricelist_item.base == "pricelist" and pricelist_item.base_pricelist_id:
541
- field_name = "price"
542
- product = product.with_context(
543
- pricelist=pricelist_item.base_pricelist_id.id
544
- )
545
- product_currency = pricelist_item.base_pricelist_id.currency_id
546
- currency_id = pricelist_item.pricelist_id.currency_id
547
-
548
- product_currency = (
549
- product_currency
550
- or (product.company_id and product.company_id.currency_id)
551
- or self.env.company.currency_id
552
- )
553
- if not currency_id:
554
- currency_id = product_currency
555
- cur_factor = 1.0
556
- else:
557
- if currency_id.id == product_currency.id:
558
- cur_factor = 1.0
559
- else:
560
- cur_factor = currency_id._get_conversion_rate(
561
- product_currency, currency_id
562
- )
563
-
564
- product_uom = product.uom_id.id
565
- if uom and uom.id != product_uom:
566
- # the unit price is in a different uom
567
- uom_factor = uom._compute_price(1.0, product.uom_id)
499
+ record.display_name = name
568
500
  else:
569
- uom_factor = 1.0
501
+ return super()._compute_display_name()
570
502
 
571
- return product[field_name] * uom_factor * cur_factor, currency_id.id
503
+ def _get_display_price(self):
504
+ # Copied and adapted from the sale module
505
+ # No need to call _get_pricelist_price_before_discount()
506
+ # since BO lines cannot have discounts
507
+ # TODO: handle combos the way Odoo does in the sale module
508
+ self.ensure_one()
509
+ pricelist_price = self._get_pricelist_price()
510
+ return pricelist_price
572
511
 
573
- def _get_display_price(self, product):
512
+ def _get_pricelist_price(self):
574
513
  # Copied and adapted from the sale module
575
514
  self.ensure_one()
576
- pricelist = self.order_id.pricelist_id
577
- partner = self.order_id.partner_id
578
- if self.order_id.pricelist_id.discount_policy == "with_discount":
579
- return product.with_context(pricelist=pricelist.id).lst_price
580
- final_price, rule_id = pricelist._get_product_price_rule(
581
- self.product_id, self.original_uom_qty or 1.0, self.product_uom
582
- )
583
- context_partner = dict(
584
- self.env.context, partner_id=partner.id, date=fields.Date.today()
515
+ self.product_id.ensure_one()
516
+ price = self.pricelist_item_id._compute_price(
517
+ product=self.product_id,
518
+ quantity=self.original_uom_qty or 1.0,
519
+ uom=self.product_uom,
520
+ date=fields.Date.today(),
521
+ currency=self.currency_id,
585
522
  )
586
- base_price, currency_id = self.with_context(
587
- **context_partner
588
- )._get_real_price_currency(
589
- self.product_id,
590
- rule_id,
591
- self.original_uom_qty,
592
- self.product_uom,
593
- pricelist.id,
594
- )
595
- if currency_id != pricelist.currency_id.id:
596
- currency = self.env["res.currency"].browse(currency_id)
597
- base_price = currency.with_context(**context_partner).compute(
598
- base_price, pricelist.currency_id
599
- )
600
- # negative discounts (= surcharge) are included in the display price
601
- return max(base_price, final_price)
523
+ return price
602
524
 
603
525
  @api.onchange("product_id", "original_uom_qty")
604
526
  def onchange_product(self):
@@ -612,23 +534,17 @@ class BlanketOrderLine(models.Model):
612
534
  if self.order_id.partner_id and float_is_zero(
613
535
  self.price_unit, precision_digits=precision
614
536
  ):
615
- self.price_unit = self._get_display_price(self.product_id)
537
+ self.price_unit = self._get_display_price()
616
538
  if self.product_id.code:
617
- name = "[{}] {}".format(name, self.product_id.code)
539
+ name = f"[{name}] {self.product_id.code}"
618
540
  if self.product_id.description_sale:
619
541
  name += "\n" + self.product_id.description_sale
620
542
  self.name = name
621
543
 
622
544
  fpos = self.order_id.fiscal_position_id
623
- if self.env.uid == SUPERUSER_ID:
624
- company_id = self.env.company.id
625
- self.taxes_id = fpos.map_tax(
626
- self.product_id.taxes_id.filtered(
627
- lambda r: r.company_id.id == company_id
628
- )
629
- )
630
- else:
631
- self.taxes_id = fpos.map_tax(self.product_id.taxes_id)
545
+ self.taxes_id = fpos.map_tax(
546
+ self.product_id.taxes_id._filter_taxes_by_company(self.company_id)
547
+ )
632
548
 
633
549
  @api.depends(
634
550
  "sale_lines.order_id.state",
@@ -663,12 +579,30 @@ class BlanketOrderLine(models.Model):
663
579
  line.remaining_uom_qty, line.product_id.uom_id
664
580
  )
665
581
 
582
+ @api.depends("product_id", "product_uom", "original_uom_qty")
583
+ def _compute_pricelist_item_id(self):
584
+ # Copied and adapted from the sale module
585
+ for line in self:
586
+ if (
587
+ not line.product_id
588
+ or line.display_type
589
+ or not line.order_id.pricelist_id
590
+ ):
591
+ line.pricelist_item_id = False
592
+ else:
593
+ line.pricelist_item_id = line.order_id.pricelist_id._get_product_rule(
594
+ line.product_id,
595
+ quantity=line.original_uom_qty or 1.0,
596
+ uom=line.product_uom,
597
+ date=fields.Date.today(),
598
+ )
599
+
666
600
  def _validate(self):
667
601
  try:
668
602
  for line in self:
669
603
  assert (
670
604
  not line.display_type and line.original_uom_qty > 0.0
671
- ) or line.display_type, _("Quantity must be greater than zero")
605
+ ) or line.display_type, self.env._("Quantity must be greater than zero")
672
606
  except AssertionError as e:
673
607
  raise UserError(e) from e
674
608
 
@@ -712,7 +646,7 @@ class BlanketOrderLine(models.Model):
712
646
  lambda line: line.display_type != values.get("display_type")
713
647
  ):
714
648
  raise UserError(
715
- _(
649
+ self.env._(
716
650
  """
717
651
  You cannot change the type of a sale order line.
718
652
  Instead you should delete the current line and create a new line
@@ -720,4 +654,4 @@ class BlanketOrderLine(models.Model):
720
654
  """
721
655
  )
722
656
  )
723
- return super(BlanketOrderLine, self).write(values)
657
+ return super().write(values)
@@ -5,7 +5,6 @@ from odoo import fields, models
5
5
 
6
6
 
7
7
  class SaleConfigSettings(models.TransientModel):
8
-
9
8
  _inherit = "res.config.settings"
10
9
 
11
10
  group_blanket_disable_adding_lines = fields.Boolean(
@@ -3,7 +3,7 @@
3
3
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4
4
  from datetime import date, timedelta
5
5
 
6
- from odoo import _, api, fields, models
6
+ from odoo import api, fields, models
7
7
  from odoo.exceptions import ValidationError
8
8
 
9
9
 
@@ -13,7 +13,7 @@ class SaleOrder(models.Model):
13
13
  blanket_order_id = fields.Many2one(
14
14
  "sale.blanket.order",
15
15
  string="Origin blanket order",
16
- compute="_compute_blanket_order_id",
16
+ related="order_line.blanket_order_line.order_id",
17
17
  )
18
18
  disable_adding_lines = fields.Boolean(
19
19
  compute="_compute_disable_adding_lines",
@@ -30,7 +30,7 @@ class SaleOrder(models.Model):
30
30
  for order in self:
31
31
  if order._check_exchausted_blanket_order_line():
32
32
  raise ValidationError(
33
- _(
33
+ self.env._(
34
34
  "Cannot confirm order %s as one of the lines refers "
35
35
  "to a blanket order that has no remaining quantity."
36
36
  )
@@ -44,18 +44,12 @@ class SaleOrder(models.Model):
44
44
  if line.blanket_order_line:
45
45
  if line.blanket_order_line.partner_id != self.partner_id:
46
46
  raise ValidationError(
47
- _(
47
+ self.env._(
48
48
  "The customer must be equal to the "
49
49
  "blanket order lines customer"
50
50
  )
51
51
  )
52
52
 
53
- @api.depends("order_line.blanket_order_line.order_id")
54
- def _compute_blanket_order_id(self):
55
- for order in self:
56
- blanket_order = order.order_line.mapped("blanket_order_line.order_id")
57
- order.blanket_order_id = blanket_order[:1]
58
-
59
53
  @api.depends("blanket_order_id")
60
54
  @api.depends_context("uid")
61
55
  def _compute_disable_adding_lines(self):
@@ -80,14 +74,14 @@ class SaleOrderLine(models.Model):
80
74
  assigned_bo_line = False
81
75
  date_planned = date.today()
82
76
  date_delta = timedelta(days=365)
83
- for line in bo_lines.filtered(lambda l: l.date_schedule):
77
+ for line in bo_lines.filtered(lambda bo_line: bo_line.date_schedule):
84
78
  date_schedule = line.date_schedule
85
79
  if date_schedule and abs(date_schedule - date_planned) < date_delta:
86
80
  assigned_bo_line = line
87
81
  date_delta = abs(date_schedule - date_planned)
88
82
  if assigned_bo_line:
89
83
  return assigned_bo_line
90
- non_date_bo_lines = bo_lines.filtered(lambda l: not l.date_schedule)
84
+ non_date_bo_lines = bo_lines.filtered(lambda bo_line: not bo_line.date_schedule)
91
85
  if non_date_bo_lines:
92
86
  return non_date_bo_lines[0]
93
87
 
@@ -185,7 +179,7 @@ class SaleOrderLine(models.Model):
185
179
  and line.product_id != line.blanket_order_line.product_id
186
180
  ):
187
181
  raise ValidationError(
188
- _(
182
+ self.env._(
189
183
  "The product in the blanket order and in the "
190
184
  "sales order must match"
191
185
  )
@@ -197,7 +191,7 @@ class SaleOrderLine(models.Model):
197
191
  if line.blanket_order_line:
198
192
  if line.currency_id != line.blanket_order_line.order_id.currency_id:
199
193
  raise ValidationError(
200
- _(
194
+ self.env._(
201
195
  "The currency of the blanket order must match with "
202
196
  "that of the sale order."
203
197
  )
@@ -0,0 +1,5 @@
1
+ Others modules provide similar features. The module (sale_order_blanket_order)[https://pypi.org/project/odoo-addon-sale-order-blanket-order] also defines the concept of sale blanket order. The main differences are:
2
+
3
+ * This module integrates Blanket Orders and Call-Off Orders into the sale.blanket.order object, whereas the other module extends the sale.order object. This means that any extensions made to the sale order model can also apply to blanket orders.
4
+
5
+ * In the other module, you can deliver and invoice directly from the blanket order. You can also create a separate call-off order to partially deliver the blanket order.
@@ -0,0 +1,17 @@
1
+ - André Pereira \<<github@andreparames.com>\> (<https://www.acsone.eu/>)
2
+
3
+ - Adrià Gil Sorribes \<<adria.gil@eficent.com>\>
4
+ (<https://www.eficent.com/>)
5
+
6
+ - Jordi Ballester Alomar \<<jordi.ballester@eficent.com>\>
7
+
8
+ - Alex Comba \<<alex.comba@agilebg.com>\> (<https://www.agilebg.com/>)
9
+
10
+ - Codeforward (https://www.codeforward.nl/):
11
+
12
+ > - Jasper Jumelet \<<jasper.jumelet@codeforward.nl>\>
13
+ > - Chris Bergman \<<chris.bergman@codeforward.nl>\>
14
+
15
+ - [Trobz](https://trobz.com):
16
+
17
+ > - Nguyễn Minh Chiến \<<chien@trobz.com>\>
@@ -1 +1,2 @@
1
- The migration of this module from 15.0 to 16.0 was financially supported by Camptocamp
1
+ The migration of this module from 15.0 to 16.0 was financially supported
2
+ by Camptocamp
@@ -0,0 +1,5 @@
1
+ A blanket order is a pre-agreement to sell a certain number of
2
+ quantities of products at a specific price. From a confirmed blanket
3
+ order, the users can create new sale orders at such price, until the
4
+ blanket order expires, either due to reaching the validity date or
5
+ exhausting all the quantities of products.
@@ -0,0 +1,3 @@
1
+ - Currently, combo products are not supported in blanket orders nor blanket
2
+ order lines, they are treated as regular products. Future versions of the
3
+ module should include support for these types of products.
@@ -0,0 +1,58 @@
1
+ A new menu in the Sales area is created, allowing users to create new
2
+ blanket orders.
3
+
4
+ To create a new Sale Blanket Order go to the sale menu in the Sales
5
+ section:
6
+
7
+ ![](../static/description/BO_menu.png)
8
+
9
+ Hitting the button create will open the form view in which we can
10
+ introduce the following information:
11
+
12
+ - Vendor
13
+
14
+ - Salesperson
15
+
16
+ - Payment Terms
17
+
18
+ - Validity date
19
+
20
+ - Order lines:
21
+ - Product
22
+ - Accorded price
23
+ - Original, Ordered, Invoiced, Received and Remaining quantities
24
+
25
+ - Terms and Conditions of the Blanket Order
26
+
27
+ ![](../static/description/BO_form.png)
28
+
29
+ From the form, once the Blanket Order has been confirmed and its state
30
+ is open, the user can create a Sale Order, check the Sale Orders
31
+ associated to the Blanket Order and/or see the Blanket Order lines
32
+ associated to the BO.
33
+
34
+ ![](../static/description/BO_actions.png)
35
+
36
+ Hitting the button Create Sale Order will open a wizard that will ask
37
+ for the amount of each product in the BO lines for which the Sale Order
38
+ will be created.
39
+
40
+ ![](../static/description/PO_from_BO.png)
41
+
42
+ Installing this module will add an additional menu which will show all
43
+ the blanket order lines currently defined in the system. From this list
44
+ the user can create customized Sale Orders selecting the lines for which
45
+ the PO (or POs if the customers are different) is (are) created.
46
+
47
+ ![](../static/description/BO_lines.png)
48
+
49
+ In the Sale Order form one field is added in the PO lines, the Blanket
50
+ Order line field. This field keeps track to which Blanket Order line the
51
+ PO line is associated. Upon adding a new product in a newly created Sale
52
+ Order a blanket order line will be suggested depending on the following
53
+ factors:
54
+
55
+ - Closer Validity date
56
+ - Remaining quantity \> Quantity introduced in the Sale Order line
57
+
58
+ ![](../static/description/PO_BOLine.png)
@@ -1,6 +1,5 @@
1
1
  <?xml version="1.0" encoding="utf-8" ?>
2
2
  <odoo>
3
-
4
3
  <record id="report_blanket_order" model="ir.actions.report">
5
4
  <field name="name">Blanket Order</field>
6
5
  <field name="model">sale.blanket.order</field>
@@ -14,5 +13,4 @@
14
13
  />
15
14
  <field name="binding_type">report</field>
16
15
  </record>
17
-
18
16
  </odoo>