Browse Source

[REF] blacken

pull/142/head
robin.keunen 4 years ago
parent
commit
ee1a5f85d0
  1. 1
      .dockerignore
  2. 3
      README.md
  3. 2
      beesdoo_account/readme/CONTRIBUTORS.rst
  4. 44
      beesdoo_base/__manifest__.py
  5. 1
      beesdoo_base/demo/eaters.xml
  6. 29
      beesdoo_base/models/membercard.py
  7. 88
      beesdoo_base/models/partner.py
  8. 2
      beesdoo_base/readme/CONTRIBUTORS.rst
  9. 4
      beesdoo_base/security/ir.model.access.csv
  10. 47
      beesdoo_base/wizard/member_card.py
  11. 18
      beesdoo_base/wizard/partner.py
  12. 30
      beesdoo_base/wizard/portal_wizard.py
  13. 23
      beesdoo_crelan_csv/__manifest__.py
  14. 2
      beesdoo_crelan_csv/models/account_journal.py
  15. 129
      beesdoo_crelan_csv/wizard/import_crelan_csv.py
  16. 47
      beesdoo_easy_my_coop/__manifest__.py
  17. 27
      beesdoo_easy_my_coop/controllers/main.py
  18. 8
      beesdoo_easy_my_coop/models/product.py
  19. 20
      beesdoo_easy_my_coop/models/res_company.py
  20. 40
      beesdoo_easy_my_coop/models/res_partner.py
  21. 13
      beesdoo_easy_my_coop/models/subscription_request.py
  22. 50
      beesdoo_easy_my_coop/tests/test_res_partner.py
  23. 13
      beesdoo_easy_my_coop/wizards/beesdoo_shift_subscribe.py
  24. 25
      beesdoo_inventory/__manifest__.py
  25. 43
      beesdoo_inventory/models/stock.py
  26. 2
      beesdoo_inventory/readme/CONTRIBUTORS.rst
  27. 2
      beesdoo_pos/__init__.py
  28. 32
      beesdoo_pos/__manifest__.py
  29. 2
      beesdoo_pos/models/__init__.py
  30. 4
      beesdoo_pos/models/beesdoo_pos.py
  31. 2
      beesdoo_pos/readme/CONTRIBUTORS.rst
  32. 2
      beesdoo_pos/static/src/css/beesdoo.css
  33. 2
      beesdoo_pos_reporting/models/res_partner.py
  34. 2
      beesdoo_pos_reporting/readme/CONTRIBUTORS.rst
  35. 38
      beesdoo_product/__manifest__.py
  36. 28
      beesdoo_product/data/product_sequence.xml
  37. 2
      beesdoo_product/models/__init__.py
  38. 281
      beesdoo_product/models/beesdoo_product.py
  39. 2
      beesdoo_product/readme/CONTRIBUTORS.rst
  40. 10
      beesdoo_product/security/ir.model.access.csv
  41. 24
      beesdoo_product/wizard/label_printing_utils.py
  42. 28
      beesdoo_product_usability/__manifest__.py
  43. 16
      beesdoo_product_usability/models/beesdoo_product.py
  44. 5
      beesdoo_purchase/__manifest__.py
  45. 38
      beesdoo_shift/__manifest__.py
  46. 1
      beesdoo_shift/models/__init__.py
  47. 295
      beesdoo_shift/models/cooperative_status.py
  48. 225
      beesdoo_shift/models/planning.py
  49. 127
      beesdoo_shift/models/res_partner.py
  50. 236
      beesdoo_shift/models/task.py
  51. 2
      beesdoo_shift/readme/CONTRIBUTORS.rst
  52. 21
      beesdoo_shift/wizard/assign_super_coop.py
  53. 72
      beesdoo_shift/wizard/batch_template.py
  54. 64
      beesdoo_shift/wizard/extension.py
  55. 41
      beesdoo_shift/wizard/holiday.py
  56. 36
      beesdoo_shift/wizard/instanciate_planning.py
  57. 193
      beesdoo_shift/wizard/subscribe.py
  58. 50
      beesdoo_shift/wizard/temporary_exemption.py
  59. 38
      beesdoo_shift_attendance/__manifest__.py
  60. 47
      beesdoo_shift_attendance/models/attendance_sheet.py
  61. 4
      beesdoo_shift_attendance/models/res_config_settings.py
  62. 22
      beesdoo_shift_attendance/tests/test_beesdoo_shift.py
  63. 8
      beesdoo_shift_attendance/wizard/generate_missing_attendance_sheets.py
  64. 7
      beesdoo_shift_attendance/wizard/validate_attendance_sheet.py
  65. 10
      beesdoo_stock/__manifest__.py
  66. 30
      beesdoo_stock/models/stock.py
  67. 2
      beesdoo_stock_coverage/models/product_template.py
  68. 1
      beesdoo_stock_coverage/readme/CONTRIBUTORS.rst
  69. 5
      beesdoo_stock_coverage/tests/test_stock_coverage.py
  70. 31
      beesdoo_website_eater/__manifest__.py
  71. 10
      beesdoo_website_eater/controllers/main.py
  72. 22
      beesdoo_website_posorder_amount/controllers/main.py
  73. 6
      beesdoo_website_shift/__manifest__.py
  74. 355
      beesdoo_website_shift/controllers/main.py
  75. 29
      beesdoo_website_shift/models/res_config.py
  76. 21
      beesdoo_website_shift/models/website.py
  77. 2
      beesdoo_website_shift/readme/CONTRIBUTORS.rst
  78. 29
      beesdoo_website_theme/__manifest__.py
  79. 2
      beesdoo_website_theme/readme/CONTRIBUTORS.rst
  80. 40
      beesdoo_worker_status/__manifest__.py
  81. 256
      beesdoo_worker_status/models/cooperative_status.py
  82. 44
      beesdoo_worker_status/models/task.py
  83. 2
      initial-data-load/01_readme.md
  84. 14
      install-odoo-docker.md
  85. 512
      install-odoo-linux-server.md
  86. 1
      install-odoo-linux.md
  87. 2
      macavrac_base/__init__.py
  88. 26
      macavrac_base/__manifest__.py
  89. 2
      macavrac_base/models/__init__.py
  90. 74
      macavrac_base/models/res_partner.py
  91. 2
      purchase_order_generator/models/product_template.py
  92. 2
      purchase_order_generator/models/purchase_order.py
  93. 5
      purchase_order_generator/models/purchase_order_generator.py
  94. 4
      purchase_order_generator/models/purchase_order_generator_line.py
  95. 2
      purchase_order_generator/tests/test_pog.py
  96. 4
      website_portal_restrict_modification/controllers/main.py

1
.dockerignore

@ -1,2 +1 @@
* *

3
README.md

@ -59,7 +59,7 @@ $ gunzip <dump-file>.sql.gz
$ psql beescoop < <dump-file>.sql $ psql beescoop < <dump-file>.sql
``` ```
##### 4) deactivate cron jobs and mails
##### 4) deactivate cron jobs and mails
``` ```
$ psql -d beescoop -c "UPDATE ir_cron SET active='f' WHERE active='t';" $ psql -d beescoop -c "UPDATE ir_cron SET active='f' WHERE active='t';"
@ -131,4 +131,3 @@ $ python odoo.py -c $ODOO_HOME/odoo.conf -u all -d beescoop --stop-after-init
insert into member_card (active, barcode, partner_id, responsible_id, activation_date) select 't', barcode, id, 1, '2016-01-01' from res_partner where barcode is not null; insert into member_card (active, barcode, partner_id, responsible_id, activation_date) select 't', barcode, id, 1, '2016-01-01' from res_partner where barcode is not null;
update res_partner set eater = 'worker_eater' where barcode is not null; update res_partner set eater = 'worker_eater' where barcode is not null;
``` ```

2
beesdoo_account/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Beescoop - Cellule IT
* Coop IT Easy SCRLfs

44
beesdoo_base/__manifest__.py

@ -6,34 +6,24 @@
# - Thibault François # - Thibault François
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': "Beescoop Base Module",
'summary': """
"name": "Beescoop Base Module",
"summary": """
Module that customize the base module and contains some python tools Module that customize the base module and contains some python tools
""", """,
'description': """
""",
'author': "Beescoop - Cellule IT",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Sales',
'version': '12.0.1.0.0',
'depends': ['point_of_sale', 'purchase', 'portal', 'partner_firstname'],
'data': [
'security/groups.xml',
'security/ir.model.access.csv',
'views/partner.xml',
'wizard/views/member_card.xml',
'wizard/views/partner.xml',
'report/beescard.xml',
"author": "Beescoop - Cellule IT, Coop IT Easy SCRLfs",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Sales",
"version": "12.0.1.0.0",
"depends": ["point_of_sale", "purchase", "portal", "partner_firstname"],
"data": [
"security/groups.xml",
"security/ir.model.access.csv",
"views/partner.xml",
"wizard/views/member_card.xml",
"wizard/views/partner.xml",
"report/beescard.xml",
], ],
'installable': True,
'demo': [
'demo/cooperators.xml',
'demo/eaters.xml',
]
"installable": True,
"demo": ["demo/cooperators.xml", "demo/eaters.xml",],
"license": "AGPL-3",
} }

1
beesdoo_base/demo/eaters.xml

@ -40,4 +40,3 @@
<field name="customer" eval="True"/> <field name="customer" eval="True"/>
</record> </record>
</odoo> </odoo>

29
beesdoo_base/models/membercard.py

@ -1,24 +1,35 @@
from odoo import models, fields, api
import uuid import uuid
class MemberCard(models.Model):
from odoo import api, fields, models
class MemberCard(models.Model):
def _get_current_user(self): def _get_current_user(self):
return self.env.uid return self.env.uid
def _compute_bar_code(self): def _compute_bar_code(self):
rule = self.env['barcode.rule'].search([('name', '=', 'Customer Barcodes')])[0]
rule = self.env["barcode.rule"].search(
[("name", "=", "Customer Barcodes")]
)[0]
size = 13 - len(rule.pattern) size = 13 - len(rule.pattern)
ean = rule.pattern + str(uuid.uuid4().fields[-1])[:size] ean = rule.pattern + str(uuid.uuid4().fields[-1])[:size]
return ean[0:12] + str(self.env['barcode.nomenclature'].ean_checksum(ean))
return ean[0:12] + str(
self.env["barcode.nomenclature"].ean_checksum(ean)
)
_name = 'member.card'
_order = 'create_date desc'
_name = "member.card"
_order = "create_date desc"
_description = "Member Card" _description = "Member Card"
valid = fields.Boolean(default=True, string="Active") valid = fields.Boolean(default=True, string="Active")
barcode = fields.Char("Barcode", oldname='ean13', default=_compute_bar_code)
partner_id = fields.Many2one('res.partner') #, default=_get_current_client)
responsible_id = fields.Many2one('res.users', default=_get_current_user, string="Responsible")
barcode = fields.Char(
"Barcode", oldname="ean13", default=_compute_bar_code
)
partner_id = fields.Many2one(
"res.partner"
) # , default=_get_current_client)
responsible_id = fields.Many2one(
"res.users", default=_get_current_user, string="Responsible"
)
end_date = fields.Date(readonly=True, string="Expiration Date") end_date = fields.Date(readonly=True, string="Expiration Date")
comment = fields.Char("Reason", required=True) comment = fields.Char("Reason", required=True)

88
beesdoo_base/models/partner.py

@ -1,24 +1,42 @@
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class Partner(models.Model): class Partner(models.Model):
_inherit = 'res.partner'
_inherit = "res.partner"
eater = fields.Selection([('eater', 'Eater'), ('worker_eater', 'Worker and Eater')], string="Eater/Worker")
child_eater_ids = fields.One2many("res.partner", "parent_eater_id", domain=[('customer', '=', True),
('eater', '=', 'eater')])
parent_eater_id = fields.Many2one("res.partner", string="Parent Worker", readonly=True)
barcode = fields.Char(compute="_get_bar_code", string='Barcode', store=True)
parent_barcode = fields.Char(compute="_get_bar_code", string='Parent Barcode', store=True)
member_card_ids = fields.One2many('member.card', 'partner_id')
eater = fields.Selection(
[("eater", "Eater"), ("worker_eater", "Worker and Eater")],
string="Eater/Worker",
)
child_eater_ids = fields.One2many(
"res.partner",
"parent_eater_id",
domain=[("customer", "=", True), ("eater", "=", "eater")],
)
parent_eater_id = fields.Many2one(
"res.partner", string="Parent Worker", readonly=True
)
barcode = fields.Char(
compute="_get_bar_code", string="Barcode", store=True
)
parent_barcode = fields.Char(
compute="_get_bar_code", string="Parent Barcode", store=True
)
member_card_ids = fields.One2many("member.card", "partner_id")
member_card_to_be_printed = fields.Boolean('Print BEES card?')
last_printed = fields.Datetime('Last printed on')
member_card_to_be_printed = fields.Boolean("Print BEES card?")
last_printed = fields.Datetime("Last printed on")
@api.depends('parent_eater_id', 'parent_eater_id.barcode', 'eater', 'member_card_ids')
@api.depends(
"parent_eater_id",
"parent_eater_id.barcode",
"eater",
"member_card_ids",
)
def _get_bar_code(self): def _get_bar_code(self):
for rec in self: for rec in self:
if rec.eater == 'eater':
if rec.eater == "eater":
rec.parent_barcode = rec.parent_eater_id.barcode rec.parent_barcode = rec.parent_eater_id.barcode
elif rec.member_card_ids: elif rec.member_card_ids:
for c in rec.member_card_ids: for c in rec.member_card_ids:
@ -29,45 +47,49 @@ class Partner(models.Model):
def write(self, values): def write(self, values):
for rec in self: for rec in self:
if ( if (
values.get('parent_eater_id')
values.get("parent_eater_id")
and rec.parent_eater_id and rec.parent_eater_id
and rec.parent_eater_id.id != values.get("parent_eater_id") and rec.parent_eater_id.id != values.get("parent_eater_id")
): ):
raise ValidationError(_('You try to assign a eater to a worker but this eater is already assign to %s please remove it before') % rec.parent_eater_id.name)
raise ValidationError(
_(
"You try to assign a eater to a worker but this eater is already assign to %s please remove it before"
)
% rec.parent_eater_id.name
)
# replace many2many command when writing on child_eater_ids to just remove the link # replace many2many command when writing on child_eater_ids to just remove the link
if 'child_eater_ids' in values:
for command in values['child_eater_ids']:
if "child_eater_ids" in values:
for command in values["child_eater_ids"]:
if command[0] == 2: if command[0] == 2:
command[0] = 3 command[0] = 3
return super(Partner, self).write(values) return super(Partner, self).write(values)
def _deactivate_active_cards(self): def _deactivate_active_cards(self):
self.ensure_one() self.ensure_one()
for card in self.member_card_ids.filtered('valid'):
for card in self.member_card_ids.filtered("valid"):
card.valid = False card.valid = False
card.end_date = fields.Date.today() card.end_date = fields.Date.today()
@api.multi @api.multi
def _new_card(self, reason, user_id, barcode=False): def _new_card(self, reason, user_id, barcode=False):
card_data = { card_data = {
'partner_id' : self.id,
'responsible_id' : user_id,
'comment' : reason,
"partner_id": self.id,
"responsible_id": user_id,
"comment": reason,
} }
if barcode: if barcode:
card_data['barcode'] = barcode
self.env['member.card'].create(card_data)
card_data["barcode"] = barcode
self.env["member.card"].create(card_data)
@api.multi @api.multi
def _new_eater(self, surname, name, email): def _new_eater(self, surname, name, email):
partner_data = { partner_data = {
'lastname' : name,
'firstname' : surname,
'is_customer' : True,
'eater' : 'eater',
'parent_eater_id' : self.id,
'email' : email,
'country_id' : self.country_id.id
}
return self.env['res.partner'].create(partner_data)
"lastname": name,
"firstname": surname,
"is_customer": True,
"eater": "eater",
"parent_eater_id": self.id,
"email": email,
"country_id": self.country_id.id,
}
return self.env["res.partner"].create(partner_data)

2
beesdoo_base/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Beescoop - Cellule IT
* Coop IT Easy SCRLfs

4
beesdoo_base/security/ir.model.access.csv

@ -1,2 +1,2 @@
"id","name","model_id/id","group_id/id","perm_read","perm_write","perm_create","perm_unlink"
"member_card_read_all","member card read all","beesdoo_base.model_member_card","","True","False","False","False"
"id","name","model_id/id","group_id/id","perm_read","perm_write","perm_create","perm_unlink"
"member_card_read_all","member card read all","beesdoo_base.model_member_card","","True","False","False","False"

47
beesdoo_base/wizard/member_card.py

@ -1,4 +1,5 @@
from odoo import models, fields, api
from odoo import api, fields, models
class NewMemberCardWizard(models.TransientModel): class NewMemberCardWizard(models.TransientModel):
""" """
@ -6,49 +7,61 @@ class NewMemberCardWizard(models.TransientModel):
The user can only define the raison why a new card is The user can only define the raison why a new card is
needed and the eater/worker that is concerned. needed and the eater/worker that is concerned.
""" """
_name = 'membercard.new.wizard'
_name = "membercard.new.wizard"
_description = "Member Card" _description = "Member Card"
def _get_default_partner(self): def _get_default_partner(self):
return self.env.context['active_id']
return self.env.context["active_id"]
new_comment = fields.Char('Reason', required=True)
partner_id = fields.Many2one('res.partner', default=_get_default_partner)
force_barcode = fields.Char('Force Barcode', groups="beesdoo_base.group_force_barcode")
new_comment = fields.Char("Reason", required=True)
partner_id = fields.Many2one("res.partner", default=_get_default_partner)
force_barcode = fields.Char(
"Force Barcode", groups="beesdoo_base.group_force_barcode"
)
@api.one @api.one
def create_new_card(self): def create_new_card(self):
client = self.partner_id.sudo() client = self.partner_id.sudo()
client._deactivate_active_cards() client._deactivate_active_cards()
client._new_card(self.new_comment, self.env.uid, barcode=self.force_barcode)
client._new_card(
self.new_comment, self.env.uid, barcode=self.force_barcode
)
client.member_card_to_be_printed = True client.member_card_to_be_printed = True
class RequestMemberCardPrintingWizard(models.TransientModel): class RequestMemberCardPrintingWizard(models.TransientModel):
_name = 'membercard.requestprinting.wizard'
_name = "membercard.requestprinting.wizard"
_description = "Member Card - Request Print Wizard" _description = "Member Card - Request Print Wizard"
def _get_selected_partners(self): def _get_selected_partners(self):
return self.env.context['active_ids']
partner_ids = fields.Many2many('res.partner', default=_get_selected_partners)
return self.env.context["active_ids"]
partner_ids = fields.Many2many(
"res.partner", default=_get_selected_partners
)
@api.one @api.one
def request_printing(self): def request_printing(self):
self.partner_ids.write({'member_card_to_be_printed' : True})
self.partner_ids.write({"member_card_to_be_printed": True})
class SetAsPrintedWizard(models.TransientModel): class SetAsPrintedWizard(models.TransientModel):
_name = 'membercard.set_as_printed.wizard'
_name = "membercard.set_as_printed.wizard"
_description = "Member card - Set as printed wizard" _description = "Member card - Set as printed wizard"
def _get_selected_partners(self): def _get_selected_partners(self):
return self.env.context['active_ids']
return self.env.context["active_ids"]
partner_ids = fields.Many2many('res.partner', default=_get_selected_partners)
partner_ids = fields.Many2many(
"res.partner", default=_get_selected_partners
)
@api.one @api.one
def set_as_printed(self): def set_as_printed(self):
self.partner_ids.write({'member_card_to_be_printed' : False,
'last_printed' : fields.Datetime.now()})
self.partner_ids.write(
{
"member_card_to_be_printed": False,
"last_printed": fields.Datetime.now(),
}
)

18
beesdoo_base/wizard/partner.py

@ -1,20 +1,22 @@
from odoo import models, fields, api
from odoo import api, fields, models
class NewEaterWizard(models.TransientModel): class NewEaterWizard(models.TransientModel):
""" """
A transient model for the creation of a eater related to a worker. A transient model for the creation of a eater related to a worker.
""" """
_name = 'eater.new.wizard'
_description = 'eater.new.wizard'
_name = "eater.new.wizard"
_description = "eater.new.wizard"
def _get_default_partner(self): def _get_default_partner(self):
return self.env.context['active_id']
return self.env.context["active_id"]
first_name = fields.Char('First Name', required=True)
last_name = fields.Char('Last Name', required=True)
email = fields.Char('Email')
first_name = fields.Char("First Name", required=True)
last_name = fields.Char("Last Name", required=True)
email = fields.Char("Email")
partner_id = fields.Many2one('res.partner', default=_get_default_partner)
partner_id = fields.Many2one("res.partner", default=_get_default_partner)
@api.one @api.one
def create_new_eater(self): def create_new_eater(self):

30
beesdoo_base/wizard/portal_wizard.py

@ -1,25 +1,29 @@
from odoo import models, fields, api
from odoo import SUPERUSER_ID
from odoo import SUPERUSER_ID, api, fields, models
class BeesdooWizard(models.TransientModel): class BeesdooWizard(models.TransientModel):
_inherit = 'portal.wizard'
_inherit = "portal.wizard"
@api.onchange('portal_id')
@api.onchange("portal_id")
def onchange_portal(self): def onchange_portal(self):
# for each partner, determine corresponding portal.wizard.user records # for each partner, determine corresponding portal.wizard.user records
res_partner = self.env['res.partner']
partner_ids = self._context.get('active_ids', [])
res_partner = self.env["res.partner"]
partner_ids = self._context.get("active_ids", [])
contact_ids = set() contact_ids = set()
for partner in res_partner.browse(partner_ids): for partner in res_partner.browse(partner_ids):
for contact in (partner.child_ids | partner):
for contact in partner.child_ids | partner:
# make sure that each contact appears at most once in the list # make sure that each contact appears at most once in the list
if contact.id not in contact_ids: if contact.id not in contact_ids:
contact_ids.add(contact.id) contact_ids.add(contact.id)
in_portal = self.portal_id in contact.user_ids.mapped('groups_id')
self.user_ids |= self.env['portal.wizard.user'].new({
'partner_id': contact.id,
'email': contact.email,
'in_portal': in_portal,
})
in_portal = self.portal_id in contact.user_ids.mapped(
"groups_id"
)
self.user_ids |= self.env["portal.wizard.user"].new(
{
"partner_id": contact.id,
"email": contact.email,
"in_portal": in_portal,
}
)

23
beesdoo_crelan_csv/__manifest__.py

@ -3,21 +3,16 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': "Beescoop Crelan Import module",
'summary': """
"name": "Beescoop Crelan Import module",
"summary": """
Import Crelan CSV Wizard Import Crelan CSV Wizard
""", """,
'description': """
"description": """
""", """,
'author': "Beescoop - Cellule IT",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Accounting & Finance',
'version': '12.0.1.0.0',
'depends': ['account_bank_statement_import'],
'installable': True,
"author": "Beescoop - Cellule IT",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Accounting & Finance",
"version": "12.0.1.0.0",
"depends": ["account_bank_statement_import"],
"installable": True,
} }

2
beesdoo_crelan_csv/models/account_journal.py

@ -6,5 +6,5 @@ class AccountJournal(models.Model):
def _get_bank_statements_available_import_formats(self): def _get_bank_statements_available_import_formats(self):
formats_list = super()._get_bank_statements_available_import_formats() formats_list = super()._get_bank_statements_available_import_formats()
formats_list.append('Crelan')
formats_list.append("Crelan")
return formats_list return formats_list

129
beesdoo_crelan_csv/wizard/import_crelan_csv.py

@ -1,8 +1,9 @@
from io import StringIO
import csv import csv
import datetime import datetime
import hashlib import hashlib
from odoo import models, _
from io import StringIO
from odoo import _, models
ACCOUNT = "Compte donneur d'ordre" ACCOUNT = "Compte donneur d'ordre"
CURRENCY = "Devise" CURRENCY = "Devise"
@ -13,83 +14,125 @@ COUNTERPART_NAME = "Contrepartie"
COMMUNICATION = "Communication" COMMUNICATION = "Communication"
TRANSACTION_TYPE = "Type d'opération" TRANSACTION_TYPE = "Type d'opération"
class CodaBankStatementImport(models.TransientModel): class CodaBankStatementImport(models.TransientModel):
_inherit = 'account.bank.statement.import'
_inherit = "account.bank.statement.import"
_date_format = "%d/%m/%Y" _date_format = "%d/%m/%Y"
_decimal_sep = "." _decimal_sep = "."
_csv_delimiter = ";" _csv_delimiter = ";"
_csv_quote = '"' _csv_quote = '"'
_header = ['Date', 'Montant', 'Devise', 'Contrepartie', 'Compte contrepartie', "Type d'opération",
'Communication', "Compte donneur d'ordre"]
_header = [
"Date",
"Montant",
"Devise",
"Contrepartie",
"Compte contrepartie",
"Type d'opération",
"Communication",
"Compte donneur d'ordre",
]
def _generate_note_crelan(self, move): def _generate_note_crelan(self, move):
notes = [] notes = []
notes.append("%s: %s" % (_('Counter Party Name'), move[COUNTERPART_NAME]))
notes.append("%s: %s" % (_('Counter Party Account'), move[COUNTERPART_NUMBER]))
notes.append("%s: %s" % (_('Communication'), move[COMMUNICATION]))
return '\n'.join(notes)
notes.append(
"{}: {}".format(_("Counter Party Name"), move[COUNTERPART_NAME])
)
notes.append(
"{}: {}".format(_("Counter Party Account"), move[COUNTERPART_NUMBER])
)
notes.append("{}: {}".format(_("Communication"), move[COMMUNICATION]))
return "\n".join(notes)
def _get_move_value_crelan(self, move, sequence): def _get_move_value_crelan(self, move, sequence):
move_data = { move_data = {
'name': move[TRANSACTION_TYPE] + ": " + move[COMMUNICATION],
'note': self._generate_note_crelan(move),
'date': self._to_iso_date(move[DATE]),
'amount': float(move[AMOUNT]),
'account_number': move[COUNTERPART_NUMBER], # Ok
'partner_name': move[COUNTERPART_NAME], # Ok
'ref': move[DATE] + '-' + move[AMOUNT] + '-' + move[COUNTERPART_NUMBER] + '-' + move[COUNTERPART_NAME],
'sequence': sequence, # Ok
'unique_import_id': move[DATE] + '-' + move[AMOUNT] + '-' + move[COUNTERPART_NUMBER] + '-' +
move[COUNTERPART_NAME] + '-' + hashlib.new('md5', move[COMMUNICATION].encode()).hexdigest()
"name": move[TRANSACTION_TYPE] + ": " + move[COMMUNICATION],
"note": self._generate_note_crelan(move),
"date": self._to_iso_date(move[DATE]),
"amount": float(move[AMOUNT]),
"account_number": move[COUNTERPART_NUMBER], # Ok
"partner_name": move[COUNTERPART_NAME], # Ok
"ref": move[DATE]
+ "-"
+ move[AMOUNT]
+ "-"
+ move[COUNTERPART_NUMBER]
+ "-"
+ move[COUNTERPART_NAME],
"sequence": sequence, # Ok
"unique_import_id": move[DATE]
+ "-"
+ move[AMOUNT]
+ "-"
+ move[COUNTERPART_NUMBER]
+ "-"
+ move[COUNTERPART_NAME]
+ "-"
+ hashlib.new("md5", move[COMMUNICATION].encode()).hexdigest(),
} }
return move_data return move_data
def _get_statement_data_crelan(self, balance_start, balance_end, begin_date, end_date):
def _get_statement_data_crelan(
self, balance_start, balance_end, begin_date, end_date
):
statement_data = { statement_data = {
'name': _("Bank Statement from %s to %s") % (begin_date, end_date),
'date': self._to_iso_date(end_date),
'balance_start': balance_start, # Ok
'balance_end_real' : balance_end, # Ok
'transactions' : []
"name": _("Bank Statement from %s to %s") % (begin_date, end_date),
"date": self._to_iso_date(end_date),
"balance_start": balance_start, # Ok
"balance_end_real": balance_end, # Ok
"transactions": [],
} }
return statement_data return statement_data
def _get_acc_number_crelan(self, acc_number): def _get_acc_number_crelan(self, acc_number):
# Check if we match the exact acc_number or the end of an acc number # Check if we match the exact acc_number or the end of an acc number
journal = self.env['account.journal'].search([('bank_acc_number', '=like', '%' + acc_number)])
journal = self.env["account.journal"].search(
[("bank_acc_number", "=like", "%" + acc_number)]
)
if not journal or len(journal) > 1: # If not found or ambiguious if not journal or len(journal) > 1: # If not found or ambiguious
return acc_number return acc_number
return journal.bank_acc_number return journal.bank_acc_number
def _get_acc_balance_crelan(self, acc_number): def _get_acc_balance_crelan(self, acc_number):
if not self.init_balance == None: if not self.init_balance == None:
return self.init_balance return self.init_balance
journal = self.env['account.journal'].search([('bank_acc_number', '=like', '%' + acc_number)])
journal = self.env["account.journal"].search(
[("bank_acc_number", "=like", "%" + acc_number)]
)
currency = journal.currency_id or journal.company_id.currency_id currency = journal.currency_id or journal.company_id.currency_id
if not journal or len(journal) > 1: # If not found or ambiguious if not journal or len(journal) > 1: # If not found or ambiguious
self.init_balance = 0.0 self.init_balance = 0.0
else: else:
lang = self._context.get('lang', 'en_US')
l = self.env['res.lang'].search([('code', '=', lang)])
balance = journal.get_journal_dashboard_datas()['last_balance'][:-1]
self.init_balance = float(balance.replace(currency.symbol, '').strip().replace(l.thousands_sep, '').replace(l.decimal_point, '.'))
lang = self._context.get("lang", "en_US")
l = self.env["res.lang"].search([("code", "=", lang)])
balance = journal.get_journal_dashboard_datas()["last_balance"][
:-1
]
self.init_balance = float(
balance.replace(currency.symbol, "")
.strip()
.replace(l.thousands_sep, "")
.replace(l.decimal_point, ".")
)
return self.init_balance return self.init_balance
def _to_iso_date(self, orig_date): def _to_iso_date(self, orig_date):
date_obj = datetime.datetime.strptime(orig_date, self._date_format)
return date_obj.strftime('%Y-%m-%d')
date_obj = datetime.datetime.strptime(orig_date, self._date_format)
return date_obj.strftime("%Y-%m-%d")
def _parse_file(self, data_file): def _parse_file(self, data_file):
try: try:
csv_file = StringIO(data_file.decode()) csv_file = StringIO(data_file.decode())
data = csv.DictReader(csv_file, delimiter=self._csv_delimiter, quotechar=self._csv_quote)
data = csv.DictReader(
csv_file,
delimiter=self._csv_delimiter,
quotechar=self._csv_quote,
)
if not data.fieldnames == self._header: if not data.fieldnames == self._header:
raise ValueError() raise ValueError()
except ValueError: except ValueError:
@ -113,6 +156,12 @@ class CodaBankStatementImport(models.TransientModel):
transactions.append(self._get_move_value_crelan(statement, i)) transactions.append(self._get_move_value_crelan(statement, i))
sum_transaction += float(statement[AMOUNT]) sum_transaction += float(statement[AMOUNT])
i += 1 i += 1
stmt = self._get_statement_data_crelan(balance, balance + sum_transaction, begin_date, end_date)
stmt['transactions'] = transactions
return currency_code, self._get_acc_number_crelan(account_number), [stmt]
stmt = self._get_statement_data_crelan(
balance, balance + sum_transaction, begin_date, end_date
)
stmt["transactions"] = transactions
return (
currency_code,
self._get_acc_number_crelan(account_number),
[stmt],
)

47
beesdoo_easy_my_coop/__manifest__.py

@ -1,35 +1,28 @@
{ {
'name': "Beescoop link with easy my coop",
'summary': """
"name": "Beescoop link with easy my coop",
"summary": """
Module that made the link between beesdoo customization Module that made the link between beesdoo customization
and easy_my_coop and easy_my_coop
""", """,
'description': """
"description": """
""", """,
'author': "BEES coop, Coop IT Easy",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Cooperative management',
'version': '12.0.1.0.0',
'depends': ['beesdoo_base',
'beesdoo_shift',
'easy_my_coop',
'easy_my_coop_website',
'partner_age',
],
'data': [
'views/res_company.xml',
'views/subscription_request.xml',
'views/subscription_templates.xml',
'views/product.xml'
"author": "BEES coop, Coop IT Easy",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Cooperative management",
"version": "12.0.1.0.0",
"depends": [
"beesdoo_base",
"beesdoo_shift",
"easy_my_coop",
"easy_my_coop_website",
"partner_age",
], ],
'demo': [
'demo/product_share.xml',
"data": [
"views/res_company.xml",
"views/subscription_request.xml",
"views/subscription_templates.xml",
"views/product.xml",
], ],
'auto_install': True,
"demo": ["demo/product_share.xml"],
"auto_install": True,
} }

27
beesdoo_easy_my_coop/controllers/main.py

@ -1,19 +1,22 @@
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from odoo.addons.easy_my_coop_website.controllers.main import WebsiteSubscription as Base
from odoo.addons.easy_my_coop_website.controllers.main import (
WebsiteSubscription as Base,
)
class WebsiteSubscription(Base):
class WebsiteSubscription(Base):
def fill_values(self, values, is_company, logged, load_from_user=False): def fill_values(self, values, is_company, logged, load_from_user=False):
values = super(WebsiteSubscription, self).fill_values(values,
is_company,
logged,
load_from_user)
cmp = request.env['res.company']._company_default_get()
values.update({
'display_info_session': cmp.display_info_session_confirmation,
'info_session_required': cmp.info_session_confirmation_required,
'info_session_text': cmp.info_session_confirmation_text,
})
values = super(WebsiteSubscription, self).fill_values(
values, is_company, logged, load_from_user
)
cmp = request.env["res.company"]._company_default_get()
values.update(
{
"display_info_session": cmp.display_info_session_confirmation,
"info_session_required": cmp.info_session_confirmation_required,
"info_session_text": cmp.info_session_confirmation_text,
}
)
return values return values

8
beesdoo_easy_my_coop/models/product.py

@ -1,9 +1,9 @@
from odoo import models, fields
from odoo import fields, models
class ProductTemplate(models.Model): class ProductTemplate(models.Model):
_inherit = 'product.template'
_inherit = "product.template"
max_nb_eater_allowed = fields.Integer( max_nb_eater_allowed = fields.Integer(
string="Max number of eater allowed", string="Max number of eater allowed",
@ -11,14 +11,14 @@ class ProductTemplate(models.Model):
help=( help=(
"Maximum number of eater allowed for the owner of the share. " "Maximum number of eater allowed for the owner of the share. "
"A negative value means no maximum." "A negative value means no maximum."
)
),
) )
allow_working = fields.Boolean( allow_working = fields.Boolean(
string="Allow owner to work?", string="Allow owner to work?",
help=( help=(
"Owner of this type of share are allowed to participate to the " "Owner of this type of share are allowed to participate to the "
"shift system." "shift system."
)
),
) )
allow_shopping = fields.Boolean( allow_shopping = fields.Boolean(
string="Allow owner to shop?", string="Allow owner to shop?",

20
beesdoo_easy_my_coop/models/res_company.py

@ -1,12 +1,12 @@
# Copyright 2019 Coop IT Easy SCRLfs # Copyright 2019 Coop IT Easy SCRLfs
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _
from odoo import _, api, fields, models
class ResCompany(models.Model): class ResCompany(models.Model):
_inherit = 'res.company'
_inherit = "res.company"
display_info_session_confirmation = fields.Boolean( display_info_session_confirmation = fields.Boolean(
help="Choose to display a info session checkbox on the cooperator" help="Choose to display a info session checkbox on the cooperator"
" website form." " website form."
@ -17,19 +17,21 @@ class ResCompany(models.Model):
info_session_confirmation_text = fields.Html( info_session_confirmation_text = fields.Html(
translate=True, translate=True,
help="Text to display aside the checkbox to confirm" help="Text to display aside the checkbox to confirm"
" participation to an info session."
" participation to an info session.",
) )
@api.onchange('info_session_confirmation_required')
@api.onchange("info_session_confirmation_required")
def onchange_info_session_confirmatio_required(self): def onchange_info_session_confirmatio_required(self):
if self.info_session_confirmation_required: if self.info_session_confirmation_required:
self.display_info_session_confirmation = True self.display_info_session_confirmation = True
_sql_constraints = [(
'info_session_approval_constraint',
"""CHECK ((info_session_confirmation_required=FALSE
_sql_constraints = [
(
"info_session_approval_constraint",
"""CHECK ((info_session_confirmation_required=FALSE
AND display_info_session_confirmation=FALSE) AND display_info_session_confirmation=FALSE)
OR display_info_session_confirmation=TRUE) OR display_info_session_confirmation=TRUE)
""", """,
"Approval can't be mandatory and not displayed."
)]
"Approval can't be mandatory and not displayed.",
)
]

40
beesdoo_easy_my_coop/models/res_partner.py

@ -1,22 +1,21 @@
# Copyright 2019-2020 Coop IT Easy SCRLfs # Copyright 2019-2020 Coop IT Easy SCRLfs
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class Partner(models.Model): class Partner(models.Model):
_inherit = 'res.partner'
_inherit = "res.partner"
info_session_confirmed = fields.Boolean( info_session_confirmed = fields.Boolean(
string="Confirmed presence to info session",
default=False,
string="Confirmed presence to info session", default=False
) )
is_worker = fields.Boolean( is_worker = fields.Boolean(
compute="_is_worker", compute="_is_worker",
search="_search_worker", search="_search_worker",
readonly=True, readonly=True,
related=""
related="",
) )
def _cooperator_share_type(self): def _cooperator_share_type(self):
@ -27,16 +26,17 @@ class Partner(models.Model):
share_type = None share_type = None
if self.cooperator_type: if self.cooperator_type:
share_type = ( share_type = (
self.env['product.template']
.search([('default_code', '=', self.cooperator_type)])
self.env["product.template"].search(
[("default_code", "=", self.cooperator_type)]
)
)[0] )[0]
return share_type return share_type
@api.depends( @api.depends(
'share_ids',
'share_ids.share_product_id',
'share_ids.share_product_id.default_code',
'share_ids.share_number',
"share_ids",
"share_ids.share_product_id",
"share_ids.share_product_id.default_code",
"share_ids.share_number",
) )
def _is_worker(self): def _is_worker(self):
""" """
@ -53,7 +53,7 @@ class Partner(models.Model):
rec.worker_store = False rec.worker_store = False
def _search_worker(self, operator, value): def _search_worker(self, operator, value):
return [('worker_store', operator, value)]
return [("worker_store", operator, value)]
@api.depends( @api.depends(
"cooperative_status_ids", "cooperative_status_ids",
@ -79,10 +79,11 @@ class Partner(models.Model):
else: else:
rec.can_shop = ( rec.can_shop = (
rec.cooperative_status_ids.can_shop rec.cooperative_status_ids.can_shop
if rec.is_worker and rec.cooperative_status_ids else False
if rec.is_worker and rec.cooperative_status_ids
else False
) )
@api.constrains('parent_eater_id')
@api.constrains("parent_eater_id")
def _check_max_parent_eaters(self): def _check_max_parent_eaters(self):
""" """
Check that the parent_eater_id in parnter in self doesn't exceed Check that the parent_eater_id in parnter in self doesn't exceed
@ -95,16 +96,15 @@ class Partner(models.Model):
if ( if (
share_type share_type
and share_type.max_nb_eater_allowed >= 0 and share_type.max_nb_eater_allowed >= 0
and len(
rec.parent_eater_id.child_eater_ids
) > share_type.max_nb_eater_allowed
and len(rec.parent_eater_id.child_eater_ids)
> share_type.max_nb_eater_allowed
): ):
raise ValidationError( raise ValidationError(
_('You can only set %d additional eaters per worker')
_("You can only set %d additional eaters per worker")
% share_type.max_nb_eater_allowed % share_type.max_nb_eater_allowed
) )
@api.constrains('child_eater_ids')
@api.constrains("child_eater_ids")
def _check_max_child_eaters(self): def _check_max_child_eaters(self):
""" """
Check the maximum number of eaters that can be assigned to a Check the maximum number of eaters that can be assigned to a
@ -119,6 +119,6 @@ class Partner(models.Model):
and len(rec.child_eater_ids) > share_type.max_nb_eater_allowed and len(rec.child_eater_ids) > share_type.max_nb_eater_allowed
): ):
raise ValidationError( raise ValidationError(
_('You can only set %d additional eaters per worker')
_("You can only set %d additional eaters per worker")
% share_type.max_nb_eater_allowed % share_type.max_nb_eater_allowed
) )

13
beesdoo_easy_my_coop/models/subscription_request.py

@ -1,26 +1,25 @@
# Copyright 2019 Coop IT Easy SCRLfs # Copyright 2019 Coop IT Easy SCRLfs
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _
from odoo import _, api, fields, models
class SubscriptionRequest(models.Model): class SubscriptionRequest(models.Model):
_inherit = 'subscription.request'
_inherit = "subscription.request"
info_session_confirmed = fields.Boolean( info_session_confirmed = fields.Boolean(
string="Confirmed Info Session",
default=False,
string="Confirmed Info Session", default=False
) )
def get_partner_vals(self): def get_partner_vals(self):
partner_vals = super(SubscriptionRequest, self).get_partner_vals() partner_vals = super(SubscriptionRequest, self).get_partner_vals()
partner_vals['info_session_confirmed'] = self.info_session_confirmed
partner_vals["info_session_confirmed"] = self.info_session_confirmed
return partner_vals return partner_vals
def get_required_field(self): def get_required_field(self):
required_fields = super(SubscriptionRequest, self).get_required_field() required_fields = super(SubscriptionRequest, self).get_required_field()
company = self.env['res.company']._company_default_get()
company = self.env["res.company"]._company_default_get()
if company.info_session_confirmation_required: if company.info_session_confirmation_required:
required_fields.append('info_session_confirmed')
required_fields.append("info_session_confirmed")
return required_fields return required_fields

50
beesdoo_easy_my_coop/tests/test_res_partner.py

@ -19,9 +19,7 @@ class TestResPartner(TransactionCase):
Test adding eater to a cooperator and raise when max is Test adding eater to a cooperator and raise when max is
reached. reached.
""" """
coop1 = self.env.ref(
"beesdoo_base.res_partner_cooperator_1_demo"
)
coop1 = self.env.ref("beesdoo_base.res_partner_cooperator_1_demo")
coop1.write({"child_eater_ids": [(4, self.eater1.id)]}) coop1.write({"child_eater_ids": [(4, self.eater1.id)]})
self.assertEqual(len(coop1.child_eater_ids), 1) self.assertEqual(len(coop1.child_eater_ids), 1)
coop1.write({"child_eater_ids": [(4, self.eater2.id)]}) coop1.write({"child_eater_ids": [(4, self.eater2.id)]})
@ -50,9 +48,7 @@ class TestResPartner(TransactionCase):
Test adding eater to a cooperator and raise when max is Test adding eater to a cooperator and raise when max is
reached. reached.
""" """
coop2 = self.env.ref(
"beesdoo_base.res_partner_cooperator_2_demo"
)
coop2 = self.env.ref("beesdoo_base.res_partner_cooperator_2_demo")
coop2.write({"child_eater_ids": [(4, self.eater1.id)]}) coop2.write({"child_eater_ids": [(4, self.eater1.id)]})
self.assertEqual(len(coop2.child_eater_ids), 1) self.assertEqual(len(coop2.child_eater_ids), 1)
coop2.write({"child_eater_ids": [(4, self.eater2.id)]}) coop2.write({"child_eater_ids": [(4, self.eater2.id)]})
@ -76,9 +72,7 @@ class TestResPartner(TransactionCase):
""" """
Test that share_c can have an unlimited number of eater. Test that share_c can have an unlimited number of eater.
""" """
coop3 = self.env.ref(
"beesdoo_base.res_partner_cooperator_3_demo"
)
coop3 = self.env.ref("beesdoo_base.res_partner_cooperator_3_demo")
coop3.write({"child_eater_ids": [(4, self.eater1.id)]}) coop3.write({"child_eater_ids": [(4, self.eater1.id)]})
self.assertEqual(len(coop3.child_eater_ids), 1) self.assertEqual(len(coop3.child_eater_ids), 1)
coop3.write({"child_eater_ids": [(4, self.eater2.id)]}) coop3.write({"child_eater_ids": [(4, self.eater2.id)]})
@ -94,9 +88,7 @@ class TestResPartner(TransactionCase):
""" """
share_c = self.env.ref("beesdoo_easy_my_coop.share_c") share_c = self.env.ref("beesdoo_easy_my_coop.share_c")
share_c.max_nb_eater_allowed = 0 share_c.max_nb_eater_allowed = 0
coop3 = self.env.ref(
"beesdoo_base.res_partner_cooperator_3_demo"
)
coop3 = self.env.ref("beesdoo_base.res_partner_cooperator_3_demo")
with self.assertRaises(ValidationError) as econtext: with self.assertRaises(ValidationError) as econtext:
coop3.write({"child_eater_ids": [(4, self.eater3.id)]}) coop3.write({"child_eater_ids": [(4, self.eater3.id)]})
self.assertIn("can only set", str(econtext.exception)) self.assertIn("can only set", str(econtext.exception))
@ -108,9 +100,7 @@ class TestResPartner(TransactionCase):
""" """
Test adding multiple eater in one write. Test adding multiple eater in one write.
""" """
coop1 = self.env.ref(
"beesdoo_base.res_partner_cooperator_1_demo"
)
coop1 = self.env.ref("beesdoo_base.res_partner_cooperator_1_demo")
coop1.write( coop1.write(
{ {
"child_eater_ids": [ "child_eater_ids": [
@ -126,9 +116,7 @@ class TestResPartner(TransactionCase):
""" """
Test adding a parent to multiple eater in one write from the eater. Test adding a parent to multiple eater in one write from the eater.
""" """
coop1 = self.env.ref(
"beesdoo_base.res_partner_cooperator_1_demo"
)
coop1 = self.env.ref("beesdoo_base.res_partner_cooperator_1_demo")
eaters = self.eater1 eaters = self.eater1
eaters |= self.eater2 eaters |= self.eater2
eaters |= self.eater3 eaters |= self.eater3
@ -139,9 +127,7 @@ class TestResPartner(TransactionCase):
""" """
Test that a cooperator is a worker based on his share type. Test that a cooperator is a worker based on his share type.
""" """
coop1 = self.env.ref(
"beesdoo_base.res_partner_cooperator_1_demo"
)
coop1 = self.env.ref("beesdoo_base.res_partner_cooperator_1_demo")
# Run computed field # Run computed field
coop1._is_worker() coop1._is_worker()
self.assertEqual(coop1.is_worker, True) self.assertEqual(coop1.is_worker, True)
@ -150,9 +136,7 @@ class TestResPartner(TransactionCase):
""" """
Test that a cooperator is a worker based on his share type. Test that a cooperator is a worker based on his share type.
""" """
coop2 = self.env.ref(
"beesdoo_base.res_partner_cooperator_2_demo"
)
coop2 = self.env.ref("beesdoo_base.res_partner_cooperator_2_demo")
# Run computed field # Run computed field
coop2._is_worker() coop2._is_worker()
self.assertEqual(coop2.is_worker, False) self.assertEqual(coop2.is_worker, False)
@ -162,12 +146,8 @@ class TestResPartner(TransactionCase):
Test that the search function returns worker based on the Test that the search function returns worker based on the
'is_worker' field. 'is_worker' field.
""" """
coop1 = self.env.ref(
"beesdoo_base.res_partner_cooperator_1_demo"
)
coop2 = self.env.ref(
"beesdoo_base.res_partner_cooperator_2_demo"
)
coop1 = self.env.ref("beesdoo_base.res_partner_cooperator_1_demo")
coop2 = self.env.ref("beesdoo_base.res_partner_cooperator_2_demo")
# Run computed field # Run computed field
coop1._is_worker() coop1._is_worker()
coop2._is_worker() coop2._is_worker()
@ -182,14 +162,12 @@ class TestResPartner(TransactionCase):
""" """
Test that a cooperator can shop based on his share type. Test that a cooperator can shop based on his share type.
""" """
coop1 = self.env.ref(
"beesdoo_base.res_partner_cooperator_1_demo"
)
coop1 = self.env.ref("beesdoo_base.res_partner_cooperator_1_demo")
# Run computed field # Run computed field
coop1._compute_can_shop() coop1._compute_can_shop()
self.assertEqual(coop1.can_shop, True) self.assertEqual(coop1.can_shop, True)
# Now unsubscribe the coop # Now unsubscribe the coop
coop1.cooperative_status_ids.status = 'resigning'
coop1.cooperative_status_ids.status = "resigning"
self.assertEqual(coop1.cooperative_status_ids.can_shop, False) self.assertEqual(coop1.cooperative_status_ids.can_shop, False)
self.assertEqual(coop1.can_shop, False) self.assertEqual(coop1.can_shop, False)
@ -197,9 +175,7 @@ class TestResPartner(TransactionCase):
""" """
Test that a cooperator can shop based on his share type. Test that a cooperator can shop based on his share type.
""" """
coop3 = self.env.ref(
"beesdoo_base.res_partner_cooperator_3_demo"
)
coop3 = self.env.ref("beesdoo_base.res_partner_cooperator_3_demo")
# Run computed field # Run computed field
coop3._compute_can_shop() coop3._compute_can_shop()
self.assertEqual(coop3.can_shop, False) self.assertEqual(coop3.can_shop, False)

13
beesdoo_easy_my_coop/wizards/beesdoo_shift_subscribe.py

@ -1,12 +1,12 @@
# Copyright 2019 Coop IT Easy SCRLfs # Copyright 2019 Coop IT Easy SCRLfs
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _
from odoo import _, api, fields, models
class Subscribe(models.TransientModel): class Subscribe(models.TransientModel):
_inherit = 'beesdoo.shift.subscribe'
_inherit = "beesdoo.shift.subscribe"
def _get_info_session_followed(self): def _get_info_session_followed(self):
""" """
@ -15,10 +15,11 @@ class Subscribe(models.TransientModel):
""" """
followed = super(Subscribe, self)._get_info_session_followed() followed = super(Subscribe, self)._get_info_session_followed()
if not followed: if not followed:
return (self.env['res.partner']
.browse(self._context.get('active_id'))
.info_session_confirmed)
return (
self.env["res.partner"]
.browse(self._context.get("active_id"))
.info_session_confirmed
)
return followed return followed
info_session = fields.Boolean(default=_get_info_session_followed) info_session = fields.Boolean(default=_get_info_session_followed)

25
beesdoo_inventory/__manifest__.py

@ -5,23 +5,18 @@
# - Jean-Marc François # - Jean-Marc François
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': "Beesdoo Inventory",
'summary': """
"name": "Beesdoo Inventory",
"summary": """
Adds a responsible, a max shipping date and a button to copy quantity to Adds a responsible, a max shipping date and a button to copy quantity to
stock pickings.""", stock pickings.""",
'description': """
"description": """
""", """,
'author': "Beescoop - Cellule IT",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Inventory',
'version': '12.0.1.0.0',
'depends': ['delivery', 'beesdoo_base', 'beesdoo_product'],
'data': [
'views/stock.xml'
],
'installable': True,
"author": "Beescoop - Cellule IT",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Inventory",
"version": "12.0.1.0.0",
"depends": ["delivery", "beesdoo_base", "beesdoo_product"],
"data": ["views/stock.xml"],
"installable": True,
} }

43
beesdoo_inventory/models/stock.py

@ -1,21 +1,40 @@
from odoo import _, api, fields, models
from odoo import _, api, fields, models
class StockPicking(models.Model): class StockPicking(models.Model):
_inherit = 'stock.picking'
_inherit = "stock.picking"
max_shipping_date = fields.Datetime("End Shipping Date") max_shipping_date = fields.Datetime("End Shipping Date")
responsible = fields.Many2one('res.partner', string="Responsible", default=lambda self: self.env.user.partner_id.id)
responsible = fields.Many2one(
"res.partner",
string="Responsible",
default=lambda self: self.env.user.partner_id.id,
)
def _add_follower(self): def _add_follower(self):
if(self.responsible):
types = self.env['mail.message.subtype'].search(['|',('res_model','=','stock.picking'),('name','=','Discussions')])
if not self.env['mail.followers'].search([('res_id', '=', self.id),
('res_model', '=', 'stock.picking'),
('partner_id', '=', self.responsible.id)]):
self.env['mail.followers'].create({'res_model' : 'stock.picking',
'res_id' : self.id,
'partner_id' : self.responsible.id,
'subtype_ids': [(6, 0, types.ids)]})
if self.responsible:
types = self.env["mail.message.subtype"].search(
[
"|",
("res_model", "=", "stock.picking"),
("name", "=", "Discussions"),
]
)
if not self.env["mail.followers"].search(
[
("res_id", "=", self.id),
("res_model", "=", "stock.picking"),
("partner_id", "=", self.responsible.id),
]
):
self.env["mail.followers"].create(
{
"res_model": "stock.picking",
"res_id": self.id,
"partner_id": self.responsible.id,
"subtype_ids": [(6, 0, types.ids)],
}
)
@api.multi @api.multi
def write(self, values): def write(self, values):

2
beesdoo_inventory/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Beescoop - Cellule IT
* Coop IT Easy SCRLfs

2
beesdoo_pos/__init__.py

@ -1 +1 @@
from . import models
from . import models

32
beesdoo_pos/__manifest__.py

@ -6,29 +6,19 @@
# - Grégoire Leeuwerck # - Grégoire Leeuwerck
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': "Beescoop Point of sale",
'summary': """
"name": "Beescoop Point of sale",
"summary": """
Module that extends the pos for the beescoop Module that extends the pos for the beescoop
""", """,
'description': """
"description": """
This module adds the eaters of the customer to the POS ActionpadWidget and PaymentScreenWidget. This module adds the eaters of the customer to the POS ActionpadWidget and PaymentScreenWidget.
""", """,
'author': "Beescoop - Cellule IT",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Point Of Sale',
'version': '12.0.1.0.0',
'depends': ['beesdoo_base', 'beesdoo_product'],
'data': [
'views/beesdoo_pos.xml',
'data/default_barcode_pattern.xml',
],
'qweb': ['static/src/xml/templates.xml'],
'installable': True,
"author": "Beescoop - Cellule IT",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Point Of Sale",
"version": "12.0.1.0.0",
"depends": ["beesdoo_base", "beesdoo_product"],
"data": ["views/beesdoo_pos.xml", "data/default_barcode_pattern.xml",],
"qweb": ["static/src/xml/templates.xml"],
"installable": True,
} }

2
beesdoo_pos/models/__init__.py

@ -1 +1 @@
from . import beesdoo_pos
from . import beesdoo_pos

4
beesdoo_pos/models/beesdoo_pos.py

@ -1,8 +1,8 @@
from odoo import models, api
from odoo import api, models
class BeescoopPosPartner(models.Model): class BeescoopPosPartner(models.Model):
_inherit = 'res.partner'
_inherit = "res.partner"
def _get_eater(self): def _get_eater(self):
eaters = [False, False, False] eaters = [False, False, False]

2
beesdoo_pos/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Beescoop - Cellule IT
* Coop IT Easy SCRLfs

2
beesdoo_pos/static/src/css/beesdoo.css

@ -31,4 +31,4 @@
.pos .actionpad .button.pay { .pos .actionpad .button.pay {
height: 108px; height: 108px;
}
}

2
beesdoo_pos_reporting/models/res_partner.py

@ -4,4 +4,4 @@ from odoo import fields, models
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = "res.partner" _inherit = "res.partner"
pos_order_count = fields.Integer(store=True,)
pos_order_count = fields.Integer(store=True)

2
beesdoo_pos_reporting/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Beescoop - Cellule IT
* Coop IT Easy SCRLfs

38
beesdoo_product/__manifest__.py

@ -6,30 +6,26 @@
# - Thibault François # - Thibault François
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': "beesdoo_product",
'summary': """
"name": "beesdoo_product",
"summary": """
Modification of product module for the needs of beescoop Modification of product module for the needs of beescoop
- SOOO5 - Ajout de label bio/ethique/provenance""", - SOOO5 - Ajout de label bio/ethique/provenance""",
'description': """
"description": """
""", """,
'author': "Beescoop - Cellule IT",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Sales',
'version': '12.0.1.0.0',
'depends': ['beesdoo_base', 'product', 'sale', 'point_of_sale'],
'data': [
'data/product_label.xml',
'data/barcode_rule.xml',
'data/product_sequence.xml',
'views/beesdoo_product.xml',
'views/assets.xml',
'wizard/views/label_printing_utils.xml',
'security/ir.model.access.csv',
"author": "Beescoop - Cellule IT",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Sales",
"version": "12.0.1.0.0",
"depends": ["beesdoo_base", "product", "sale", "point_of_sale"],
"data": [
"data/product_label.xml",
"data/barcode_rule.xml",
"data/product_sequence.xml",
"views/beesdoo_product.xml",
"views/assets.xml",
"wizard/views/label_printing_utils.xml",
"security/ir.model.access.csv",
], ],
'installable': True,
"installable": True,
} }

28
beesdoo_product/data/product_sequence.xml

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="seq_ean_product_internal_ref" model="ir.sequence">
<field name="name">Internal reference</field>
<field name="code">product.internal.code</field>
<field name="prefix"></field>
<field name="padding">5</field>
<field name="suffix"></field>
<field name="number_next">1</field>
<!-- <field name="barcode_sequence" eval="True"/> -->
</record>
</data>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="seq_ean_product_internal_ref" model="ir.sequence">
<field name="name">Internal reference</field>
<field name="code">product.internal.code</field>
<field name="prefix"></field>
<field name="padding">5</field>
<field name="suffix"></field>
<field name="number_next">1</field>
<!-- <field name="barcode_sequence" eval="True"/> -->
</record>
</data>
</odoo>

2
beesdoo_product/models/__init__.py

@ -1 +1 @@
from . import beesdoo_product
from . import beesdoo_product

281
beesdoo_product/models/beesdoo_product.py

@ -1,59 +1,93 @@
import uuid import uuid
from odoo import models, fields, api
from odoo.tools.translate import _
from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
from odoo.tools.translate import _
class BeesdooProduct(models.Model): class BeesdooProduct(models.Model):
_inherit = "product.template" _inherit = "product.template"
eco_label = fields.Many2one('beesdoo.product.label', domain=[('type', '=', 'eco')])
local_label = fields.Many2one('beesdoo.product.label', domain=[('type', '=', 'local')])
fair_label = fields.Many2one('beesdoo.product.label', domain=[('type', '=', 'fair')])
origin_label = fields.Many2one('beesdoo.product.label', domain=[('type', '=', 'delivery')])
eco_label = fields.Many2one(
"beesdoo.product.label", domain=[("type", "=", "eco")]
)
local_label = fields.Many2one(
"beesdoo.product.label", domain=[("type", "=", "local")]
)
fair_label = fields.Many2one(
"beesdoo.product.label", domain=[("type", "=", "fair")]
)
origin_label = fields.Many2one(
"beesdoo.product.label", domain=[("type", "=", "delivery")]
)
main_seller_id = fields.Many2one('res.partner', string='Main Seller', compute='_compute_main_seller_id', store=True)
main_seller_id = fields.Many2one(
"res.partner",
string="Main Seller",
compute="_compute_main_seller_id",
store=True,
)
display_unit = fields.Many2one('uom.uom')
default_reference_unit = fields.Many2one('uom.uom')
display_weight = fields.Float(compute='_get_display_weight', store=True)
display_unit = fields.Many2one("uom.uom")
default_reference_unit = fields.Many2one("uom.uom")
display_weight = fields.Float(compute="_get_display_weight", store=True)
total_with_vat = fields.Float(compute='_get_total', store=True, string="Total Sales Price with VAT")
total_with_vat_by_unit = fields.Float(compute='_get_total', store=True, string="Total Sales Price with VAT by Reference Unit")
total_deposit = fields.Float(compute='_get_total', store=True, string="Deposit Price")
total_with_vat = fields.Float(
compute="_get_total", store=True, string="Total Sales Price with VAT"
)
total_with_vat_by_unit = fields.Float(
compute="_get_total",
store=True,
string="Total Sales Price with VAT by Reference Unit",
)
total_deposit = fields.Float(
compute="_get_total", store=True, string="Deposit Price"
)
label_to_be_printed = fields.Boolean('Print label?')
label_last_printed = fields.Datetime('Label last printed on')
label_to_be_printed = fields.Boolean("Print label?")
label_last_printed = fields.Datetime("Label last printed on")
note = fields.Text("Comments")
note = fields.Text('Comments')
# S0023 : List_price = Price HTVA, so add a suggested price # S0023 : List_price = Price HTVA, so add a suggested price
list_price = fields.Float(string='exVAT Price')
suggested_price = fields.Float(string='Suggested exVAT Price', compute='_compute_cost', readOnly=True)
list_price = fields.Float(string="exVAT Price")
suggested_price = fields.Float(
string="Suggested exVAT Price", compute="_compute_cost", readOnly=True
)
deadline_for_sale = fields.Integer(string="Deadline for sale(days)") deadline_for_sale = fields.Integer(string="Deadline for sale(days)")
deadline_for_consumption = fields.Integer(string="Deadline for consumption(days)")
deadline_for_consumption = fields.Integer(
string="Deadline for consumption(days)"
)
ingredients = fields.Char(string="Ingredient") ingredients = fields.Char(string="Ingredient")
scale_label_info_1 = fields.Char(string="Scale lable info 1") scale_label_info_1 = fields.Char(string="Scale lable info 1")
scale_label_info_2 = fields.Char(string="Scale lable info 2") scale_label_info_2 = fields.Char(string="Scale lable info 2")
scale_sale_unit = fields.Char(compute="_get_scale_sale_uom", string="Scale sale unit", store=True)
scale_category = fields.Many2one('beesdoo.scale.category', string="Scale Category")
scale_category_code = fields.Integer(related='scale_category.code', string="Scale category code", readonly=True, store=True)
@api.depends('uom_id','uom_id.category_id','uom_id.category_id.type')
scale_sale_unit = fields.Char(
compute="_get_scale_sale_uom", string="Scale sale unit", store=True
)
scale_category = fields.Many2one(
"beesdoo.scale.category", string="Scale Category"
)
scale_category_code = fields.Integer(
related="scale_category.code",
string="Scale category code",
readonly=True,
store=True,
)
@api.depends("uom_id", "uom_id.category_id", "uom_id.category_id.type")
@api.multi @api.multi
def _get_scale_sale_uom(self): def _get_scale_sale_uom(self):
for product in self: for product in self:
if product.uom_id.category_id.type == 'unit':
product.scale_sale_unit = 'F'
elif product.uom_id.category_id.type == 'weight':
product.scale_sale_unit = 'P'
if product.uom_id.category_id.type == "unit":
product.scale_sale_unit = "F"
elif product.uom_id.category_id.type == "weight":
product.scale_sale_unit = "P"
def _get_main_supplier_info(self): def _get_main_supplier_info(self):
suppliers = self.seller_ids.sorted( suppliers = self.seller_ids.sorted(
key=lambda seller: seller.date_start,
reverse=True)
key=lambda seller: seller.date_start, reverse=True
)
if suppliers: if suppliers:
return suppliers[0] return suppliers[0]
else: else:
@ -62,84 +96,140 @@ class BeesdooProduct(models.Model):
@api.one @api.one
def generate_barcode(self): def generate_barcode(self):
if self.to_weight: if self.to_weight:
seq_internal_code = self.env.ref('beesdoo_product.seq_ean_product_internal_ref')
bc = ''
seq_internal_code = self.env.ref(
"beesdoo_product.seq_ean_product_internal_ref"
)
bc = ""
if not self.default_code: if not self.default_code:
rule = self.env['barcode.rule'].search([('name', '=', 'Price Barcodes (Computed Weight) 2 Decimals')])[0]
rule = self.env["barcode.rule"].search(
[
(
"name",
"=",
"Price Barcodes (Computed Weight) 2 Decimals",
)
]
)[0]
default_code = seq_internal_code.next_by_id() default_code = seq_internal_code.next_by_id()
while(self.search_count([('default_code', '=', default_code)]) > 1):
while (
self.search_count([("default_code", "=", default_code)])
> 1
):
default_code = seq_internal_code.next_by_id() default_code = seq_internal_code.next_by_id()
self.default_code = default_code self.default_code = default_code
ean = '02' + self.default_code[0:5] + '000000'
bc = ean[0:12] + str(self.env['barcode.nomenclature'].ean_checksum(ean))
ean = "02" + self.default_code[0:5] + "000000"
bc = ean[0:12] + str(
self.env["barcode.nomenclature"].ean_checksum(ean)
)
else: else:
rule = self.env['barcode.rule'].search([('name', '=', 'Beescoop Product Barcodes')])[0]
rule = self.env["barcode.rule"].search(
[("name", "=", "Beescoop Product Barcodes")]
)[0]
size = 13 - len(rule.pattern) size = 13 - len(rule.pattern)
ean = rule.pattern + str(uuid.uuid4().fields[-1])[:size] ean = rule.pattern + str(uuid.uuid4().fields[-1])[:size]
bc = ean[0:12] + str(self.env['barcode.nomenclature'].ean_checksum(ean))
bc = ean[0:12] + str(
self.env["barcode.nomenclature"].ean_checksum(ean)
)
# Make sure there is no other active member with the same barcode # Make sure there is no other active member with the same barcode
while(self.search_count([('barcode', '=', bc)]) > 1):
while self.search_count([("barcode", "=", bc)]) > 1:
ean = rule.pattern + str(uuid.uuid4().fields[-1])[:size] ean = rule.pattern + str(uuid.uuid4().fields[-1])[:size]
bc = ean[0:12] + str(self.env['barcode.nomenclature'].ean_checksum(ean))
print('barcode :', bc)
bc = ean[0:12] + str(
self.env["barcode.nomenclature"].ean_checksum(ean)
)
print("barcode :", bc)
self.barcode = bc self.barcode = bc
@api.one @api.one
@api.depends('seller_ids', 'seller_ids.date_start')
@api.depends("seller_ids", "seller_ids.date_start")
def _compute_main_seller_id(self): def _compute_main_seller_id(self):
# Calcule le vendeur associé qui a la date de début la plus récente et plus petite qu’aujourd’hui # Calcule le vendeur associé qui a la date de début la plus récente et plus petite qu’aujourd’hui
sellers_ids = self._get_main_supplier_info() # self.seller_ids.sorted(key=lambda seller: seller.date_start, reverse=True)
sellers_ids = (
self._get_main_supplier_info()
) # self.seller_ids.sorted(key=lambda seller: seller.date_start, reverse=True)
self.main_seller_id = sellers_ids and sellers_ids[0].name or False self.main_seller_id = sellers_ids and sellers_ids[0].name or False
@api.one @api.one
@api.depends('taxes_id', 'list_price', 'taxes_id.amount',
'taxes_id.tax_group_id',
'display_weight', 'weight')
@api.depends(
"taxes_id",
"list_price",
"taxes_id.amount",
"taxes_id.tax_group_id",
"display_weight",
"weight",
)
def _get_total(self): def _get_total(self):
consignes_group = self.env.ref('beesdoo_product.consignes_group_tax',
raise_if_not_found=False)
consignes_group = self.env.ref(
"beesdoo_product.consignes_group_tax", raise_if_not_found=False
)
taxes_included = set(self.taxes_id.mapped('price_include'))
taxes_included = set(self.taxes_id.mapped("price_include"))
if len(taxes_included) == 0: if len(taxes_included) == 0:
self.total_with_vat = self.list_price self.total_with_vat = self.list_price
return True return True
elif len(taxes_included) > 1: elif len(taxes_included) > 1:
raise ValidationError('Several tax strategies (price_include) defined for %s' % self.name)
raise ValidationError(
"Several tax strategies (price_include) defined for %s"
% self.name
)
elif taxes_included.pop(): elif taxes_included.pop():
self.total_with_vat = self.list_price self.total_with_vat = self.list_price
self.total_deposit = sum([tax._compute_amount(self.list_price, self.list_price) for tax in self.taxes_id if tax.tax_group_id == consignes_group])
self.total_deposit = sum(
[
tax._compute_amount(self.list_price, self.list_price)
for tax in self.taxes_id
if tax.tax_group_id == consignes_group
]
)
else: else:
tax_amount_sum = sum([tax._compute_amount(self.list_price, self.list_price)
for tax in self.taxes_id
if tax.tax_group_id != consignes_group])
tax_amount_sum = sum(
[
tax._compute_amount(self.list_price, self.list_price)
for tax in self.taxes_id
if tax.tax_group_id != consignes_group
]
)
self.total_with_vat = self.list_price + tax_amount_sum self.total_with_vat = self.list_price + tax_amount_sum
self.total_deposit = sum([tax._compute_amount(self.list_price, self.list_price)
for tax in self.taxes_id
if tax.tax_group_id == consignes_group])
self.total_deposit = sum(
[
tax._compute_amount(self.list_price, self.list_price)
for tax in self.taxes_id
if tax.tax_group_id == consignes_group
]
)
if self.display_weight > 0: if self.display_weight > 0:
self.total_with_vat_by_unit = self.total_with_vat / self.weight self.total_with_vat_by_unit = self.total_with_vat / self.weight
@api.one @api.one
@api.depends('weight', 'display_unit')
@api.depends("weight", "display_unit")
def _get_display_weight(self): def _get_display_weight(self):
self.display_weight = self.weight * self.display_unit.factor self.display_weight = self.weight * self.display_unit.factor
@api.one @api.one
@api.constrains('display_unit', 'default_reference_unit')
@api.constrains("display_unit", "default_reference_unit")
def _unit_same_category(self): def _unit_same_category(self):
if self.display_unit.category_id != self.default_reference_unit.category_id:
raise UserError(_('Reference Unit and Display Unit should belong to the same category'))
if (
self.display_unit.category_id
!= self.default_reference_unit.category_id
):
raise UserError(
_(
"Reference Unit and Display Unit should belong to the same category"
)
)
@api.one @api.one
@api.depends('seller_ids')
@api.depends("seller_ids")
def _compute_cost(self): def _compute_cost(self):
suppliers = self._get_main_supplier_info() suppliers = self._get_main_supplier_info()
if(len(suppliers) > 0):
self.suggested_price = (suppliers[0].price * self.uom_po_id.factor)* (1 + suppliers[0].product_tmpl_id.categ_id.profit_margin / 100)
if len(suppliers) > 0:
self.suggested_price = (
suppliers[0].price * self.uom_po_id.factor
) * (1 + suppliers[0].product_tmpl_id.categ_id.profit_margin / 100)
class BeesdooScaleCategory(models.Model): class BeesdooScaleCategory(models.Model):
_name = "beesdoo.scale.category" _name = "beesdoo.scale.category"
@ -147,43 +237,64 @@ class BeesdooScaleCategory(models.Model):
name = fields.Char(string="Scale name category") name = fields.Char(string="Scale name category")
code = fields.Integer(string="Category code") code = fields.Integer(string="Category code")
_sql_constraints = [ _sql_constraints = [
('code_scale_categ_uniq', 'unique (code)', 'The code of the scale category must be unique !')
(
"code_scale_categ_uniq",
"unique (code)",
"The code of the scale category must be unique !",
)
] ]
class BeesdooProductLabel(models.Model): class BeesdooProductLabel(models.Model):
_name = "beesdoo.product.label" _name = "beesdoo.product.label"
_description = "beesdoo.product.label" _description = "beesdoo.product.label"
name = fields.Char() name = fields.Char()
type = fields.Selection([('eco', 'Écologique'), ('local', 'Local'), ('fair', 'Équitable'), ('delivery', 'Distribution')])
type = fields.Selection(
[
("eco", "Écologique"),
("local", "Local"),
("fair", "Équitable"),
("delivery", "Distribution"),
]
)
color_code = fields.Char() color_code = fields.Char()
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
class BeesdooProductCategory(models.Model): class BeesdooProductCategory(models.Model):
_inherit = "product.category" _inherit = "product.category"
profit_margin = fields.Float(default = '10.0', string = "Product Margin [%]")
profit_margin = fields.Float(default="10.0", string="Product Margin [%]")
@api.one @api.one
@api.constrains('profit_margin')
@api.constrains("profit_margin")
def _check_margin(self): def _check_margin(self):
if (self.profit_margin < 0.0):
raise UserError(_('Percentages for Profit Margin must > 0.'))
if self.profit_margin < 0.0:
raise UserError(_("Percentages for Profit Margin must > 0."))
class BeesdooProductSupplierInfo(models.Model): class BeesdooProductSupplierInfo(models.Model):
_inherit = "product.supplierinfo" _inherit = "product.supplierinfo"
price = fields.Float('exVAT Price')
price = fields.Float("exVAT Price")
class BeesdooUOMCateg(models.Model): class BeesdooUOMCateg(models.Model):
_inherit = 'uom.category'
type = fields.Selection([('unit','Unit'),
('weight','Weight'),
('time','Time'),
('distance','Distance'),
('surface','Surface'),
('volume','Volume'),
('other','Other')],string='Category type',default='unit')
_inherit = "uom.category"
type = fields.Selection(
[
("unit", "Unit"),
("weight", "Weight"),
("time", "Time"),
("distance", "Distance"),
("surface", "Surface"),
("volume", "Volume"),
("other", "Other"),
],
string="Category type",
default="unit",
)

2
beesdoo_product/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Beescoop - Cellule IT
* Coop IT Easy SCRLfs

10
beesdoo_product/security/ir.model.access.csv

@ -1,5 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
beesdoo_product_label_read_all,beesdoo.product.label Read All,model_beesdoo_product_label,,1,0,0,0
beesdoo_product_label_all_access_sale_manager,beesdoo.product.label All Access Sale Manager,model_beesdoo_product_label,sales_team.group_sale_manager,1,1,1,1
beesdoo_scale_category_read_all,beesdoo.scale.category Read All,model_beesdoo_scale_category,,1,0,0,0
beesdoo_scale_categoryl_all_access_sale_manager,beesdoo.scale.category All Access Sale Manager,model_beesdoo_scale_category,sales_team.group_sale_manager,1,1,1,0
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
beesdoo_product_label_read_all,beesdoo.product.label Read All,model_beesdoo_product_label,,1,0,0,0
beesdoo_product_label_all_access_sale_manager,beesdoo.product.label All Access Sale Manager,model_beesdoo_product_label,sales_team.group_sale_manager,1,1,1,1
beesdoo_scale_category_read_all,beesdoo.scale.category Read All,model_beesdoo_scale_category,,1,0,0,0
beesdoo_scale_categoryl_all_access_sale_manager,beesdoo.scale.category All Access Sale Manager,model_beesdoo_scale_category,sales_team.group_sale_manager,1,1,1,0

24
beesdoo_product/wizard/label_printing_utils.py

@ -1,20 +1,26 @@
from odoo import models, fields, api
from odoo import api, fields, models
class RequestLabelPrintingWizard(models.TransientModel): class RequestLabelPrintingWizard(models.TransientModel):
_name = 'label.printing.wizard'
_description = 'label.printing.wizard'
_name = "label.printing.wizard"
_description = "label.printing.wizard"
def _get_selected_products(self): def _get_selected_products(self):
return self.env.context['active_ids']
product_ids = fields.Many2many('product.template', default=_get_selected_products)
return self.env.context["active_ids"]
product_ids = fields.Many2many(
"product.template", default=_get_selected_products
)
@api.one @api.one
def request_printing(self): def request_printing(self):
self.product_ids.write({'label_to_be_printed' : True})
self.product_ids.write({"label_to_be_printed": True})
@api.one @api.one
def set_as_printed(self): def set_as_printed(self):
self.product_ids.write({'label_to_be_printed' : False, 'label_last_printed' : fields.Datetime.now()})
self.product_ids.write(
{
"label_to_be_printed": False,
"label_last_printed": fields.Datetime.now(),
}
)

28
beesdoo_product_usability/__manifest__.py

@ -1,22 +1,18 @@
# Copyright 2017 - 2020 BEES coop SCRLfs # Copyright 2017 - 2020 BEES coop SCRLfs
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': "BEES coop Product Usability",
'description': """
Adapt the
"name": "BEES coop Product Usability",
"summary": """
Adapt the product views.
""", """,
'author': "Beescoop - Cellule IT, Coop IT Easy",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Sales Management',
'version': '12.0.1.0.0',
'depends': [
'beesdoo_product',
'beesdoo_stock_coverage',
'beesdoo_purchase',
],
'data': [
'views/beesdoo_product.xml',
"author": "Beescoop - Cellule IT, Coop IT Easy SCRLfs",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Sales Management",
"version": "12.0.1.0.0",
"depends": [
"beesdoo_product",
"beesdoo_stock_coverage",
"beesdoo_purchase",
], ],
"data": ["views/beesdoo_product.xml",],
} }

16
beesdoo_product_usability/models/beesdoo_product.py

@ -1,25 +1,23 @@
from odoo import models, fields, api
from odoo import api, fields, models
class BeesdooProduct(models.Model): class BeesdooProduct(models.Model):
_inherit = "product.template" _inherit = "product.template"
main_supplierinfo = fields.Many2one( main_supplierinfo = fields.Many2one(
'product.supplierinfo',
string='Main Supplier Information',
compute='_compute_main_supplierinfo'
"product.supplierinfo",
string="Main Supplier Information",
compute="_compute_main_supplierinfo",
) )
main_price = fields.Float( main_price = fields.Float(
string='Price',
compute='_compute_main_supplierinfo',
string="Price", compute="_compute_main_supplierinfo"
) )
main_minimum_qty = fields.Float( main_minimum_qty = fields.Float(
string='Minimum Quantity',
compute='_compute_main_supplierinfo',
string="Minimum Quantity", compute="_compute_main_supplierinfo"
) )
@api.multi @api.multi
@api.depends('seller_ids')
@api.depends("seller_ids")
def _compute_main_supplierinfo(self): def _compute_main_supplierinfo(self):
for product in self: for product in self:
supplierinfo = product._get_main_supplier_info() supplierinfo = product._get_main_supplier_info()

5
beesdoo_purchase/__manifest__.py

@ -18,8 +18,5 @@
"category": "Purchase", "category": "Purchase",
"version": "12.0.1.1.0", "version": "12.0.1.1.0",
"depends": ["base", "purchase", "beesdoo_product"], "depends": ["base", "purchase", "beesdoo_product"],
"data": [
"views/purchase_order.xml",
"report/report_purchaseorder.xml",
],
"data": ["views/purchase_order.xml", "report/report_purchaseorder.xml",],
} }

38
beesdoo_shift/__manifest__.py

@ -1,24 +1,18 @@
{
'name': "Beescoop Shift Management",
'summary': """
Volonteer Timetable Management""",
'description': """
""",
# Copyright 2020 Coop IT Easy SCRL fs
# Elouan Le Bars <elouan@coopiteasy.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
'author': "THibault Francois, Elouan Le Bars, Coop It Easy",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Cooperative management',
'version': '12.0.1.0.0',
'depends': [
'mail',
],
'data': [
{
"name": "Beescoop Shift Management",
"summary": """
Volonteer Timetable Management""",
"author": "THibault Francois, Elouan Le Bars, Coop It Easy",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Cooperative management",
"version": "12.0.1.0.0",
"depends": ["mail",],
"data": [
"data/system_parameter.xml", "data/system_parameter.xml",
"data/cron.xml", "data/cron.xml",
"data/mail_template.xml", "data/mail_template.xml",
@ -38,8 +32,6 @@
"wizard/holiday.xml", "wizard/holiday.xml",
"wizard/temporary_exemption.xml", "wizard/temporary_exemption.xml",
], ],
'demo': [
"demo/templates.xml",
"demo/workers.xml",
]
"demo": ["demo/templates.xml", "demo/workers.xml",],
"license": "AGPL-3",
} }

1
beesdoo_shift/models/__init__.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from . import task from . import task
from . import planning from . import planning
from . import cooperative_status from . import cooperative_status

295
beesdoo_shift/models/cooperative_status.py

@ -1,149 +1,210 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError, UserError
from datetime import timedelta, datetime
import logging import logging
from datetime import datetime, timedelta
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
def add_days_delta(date_from, days_delta): def add_days_delta(date_from, days_delta):
if not date_from: if not date_from:
return date_from return date_from
next_date = date_from + timedelta(days=days_delta) next_date = date_from + timedelta(days=days_delta)
return next_date return next_date
class ExemptReason(models.Model): class ExemptReason(models.Model):
_name = 'cooperative.exempt.reason'
_description = 'cooperative.exempt.reason'
_name = "cooperative.exempt.reason"
_description = "cooperative.exempt.reason"
name = fields.Char(required=True) name = fields.Char(required=True)
class HistoryStatus(models.Model): class HistoryStatus(models.Model):
_name = 'cooperative.status.history'
_description = 'cooperative.status.history'
_name = "cooperative.status.history"
_description = "cooperative.status.history"
_order= 'create_date desc'
_order = "create_date desc"
status_id = fields.Many2one('cooperative.status')
cooperator_id = fields.Many2one('res.partner')
status_id = fields.Many2one("cooperative.status")
cooperator_id = fields.Many2one("res.partner")
change = fields.Char() change = fields.Char()
type = fields.Selection([('status', 'Status Change'), ('counter', 'Counter Change')])
user_id = fields.Many2one('res.users', string="User")
type = fields.Selection(
[("status", "Status Change"), ("counter", "Counter Change")]
)
user_id = fields.Many2one("res.users", string="User")
class CooperativeStatus(models.Model): class CooperativeStatus(models.Model):
_name = 'cooperative.status'
_description = 'cooperative.status'
_rec_name = 'cooperator_id'
_order = 'cooperator_id'
_name = "cooperative.status"
_description = "cooperative.status"
_rec_name = "cooperator_id"
_order = "cooperator_id"
_period = 28 _period = 28
def _get_status(self): def _get_status(self):
return [ return [
('ok', 'Up to Date'),
('holiday', 'Holidays'),
('alert', 'Alerte'),
('extension', 'Extension'),
('suspended', 'Suspended'),
('exempted', 'Exempted'),
('unsubscribed', 'Unsubscribed'),
('resigning', 'Resigning')
("ok", "Up to Date"),
("holiday", "Holidays"),
("alert", "Alerte"),
("extension", "Extension"),
("suspended", "Suspended"),
("exempted", "Exempted"),
("unsubscribed", "Unsubscribed"),
("resigning", "Resigning"),
] ]
today = fields.Date(help="Field that allow to compute field and store them even if they are based on the current date", default=fields.Date.today)
cooperator_id = fields.Many2one('res.partner')
active = fields.Boolean(related="cooperator_id.active", store=True, index=True)
info_session = fields.Boolean('Information Session ?')
info_session_date = fields.Date('Information Session Date')
today = fields.Date(
help="Field that allow to compute field and store them even if they are based on the current date",
default=fields.Date.today,
)
cooperator_id = fields.Many2one("res.partner")
active = fields.Boolean(
related="cooperator_id.active", store=True, index=True
)
info_session = fields.Boolean("Information Session ?")
info_session_date = fields.Date("Information Session Date")
super = fields.Boolean("Super Cooperative") super = fields.Boolean("Super Cooperative")
sr = fields.Integer("Regular shifts counter", default=0) sr = fields.Integer("Regular shifts counter", default=0)
sc = fields.Integer("Compensation shifts counter", default=0) sc = fields.Integer("Compensation shifts counter", default=0)
time_extension = fields.Integer("Extension Days NB", default=0, help="Addtional days to the automatic extension, 5 mean that you have a total of 15 extension days of default one is set to 10")
time_extension = fields.Integer(
"Extension Days NB",
default=0,
help="Addtional days to the automatic extension, 5 mean that you have a total of 15 extension days of default one is set to 10",
)
holiday_start_time = fields.Date("Holidays Start Day") holiday_start_time = fields.Date("Holidays Start Day")
holiday_end_time = fields.Date("Holidays End Day") holiday_end_time = fields.Date("Holidays End Day")
alert_start_time = fields.Date("Alert Start Day") alert_start_time = fields.Date("Alert Start Day")
extension_start_time = fields.Date("Extension Start Day") extension_start_time = fields.Date("Extension Start Day")
working_mode = fields.Selection( working_mode = fields.Selection(
[ [
('regular', 'Regular worker'),
('irregular', 'Irregular worker'),
('exempt', 'Exempted'),
("regular", "Regular worker"),
("irregular", "Irregular worker"),
("exempt", "Exempted"),
], ],
string="Working mode"
string="Working mode",
)
exempt_reason_id = fields.Many2one(
"cooperative.exempt.reason", "Exempt Reason"
)
status = fields.Selection(
selection=_get_status,
compute="_compute_status",
string="Cooperative Status",
store=True,
)
can_shop = fields.Boolean(compute="_compute_can_shop", store=True)
history_ids = fields.One2many(
"cooperative.status.history", "status_id", readonly=True
) )
exempt_reason_id = fields.Many2one('cooperative.exempt.reason', 'Exempt Reason')
status = fields.Selection(selection=_get_status,
compute="_compute_status", string="Cooperative Status", store=True)
can_shop = fields.Boolean(compute='_compute_can_shop', store=True)
history_ids = fields.One2many('cooperative.status.history', 'status_id', readonly=True)
unsubscribed = fields.Boolean(default=False, help="Manually unsubscribed") unsubscribed = fields.Boolean(default=False, help="Manually unsubscribed")
resigning = fields.Boolean(default=False, help="Want to leave the beescoop")
resigning = fields.Boolean(
default=False, help="Want to leave the beescoop"
)
#Specific to irregular
irregular_start_date = fields.Date() #TODO migration script
# Specific to irregular
irregular_start_date = fields.Date() # TODO migration script
irregular_absence_date = fields.Date() irregular_absence_date = fields.Date()
irregular_absence_counter = fields.Integer() #TODO unsubscribe when reach -2
future_alert_date = fields.Date(compute='_compute_future_alert_date')
next_countdown_date = fields.Date(compute='_compute_next_countdown_date')
temporary_exempt_reason_id = fields.Many2one('cooperative.exempt.reason', 'Exempt Reason')
irregular_absence_counter = (
fields.Integer()
) # TODO unsubscribe when reach -2
future_alert_date = fields.Date(compute="_compute_future_alert_date")
next_countdown_date = fields.Date(compute="_compute_next_countdown_date")
temporary_exempt_reason_id = fields.Many2one(
"cooperative.exempt.reason", "Exempt Reason"
)
temporary_exempt_start_date = fields.Date() temporary_exempt_start_date = fields.Date()
temporary_exempt_end_date = fields.Date() temporary_exempt_end_date = fields.Date()
@api.depends('status')
@api.depends("status")
def _compute_can_shop(self): def _compute_can_shop(self):
for rec in self: for rec in self:
rec.can_shop = rec.status in self._can_shop_status() rec.can_shop = rec.status in self._can_shop_status()
@api.depends('today', 'sr', 'sc', 'holiday_end_time',
'holiday_start_time', 'time_extension',
'alert_start_time', 'extension_start_time',
'unsubscribed', 'irregular_absence_date',
'irregular_absence_counter', 'temporary_exempt_start_date',
'temporary_exempt_end_date', 'resigning', 'cooperator_id.subscribed_shift_ids')
@api.depends(
"today",
"sr",
"sc",
"holiday_end_time",
"holiday_start_time",
"time_extension",
"alert_start_time",
"extension_start_time",
"unsubscribed",
"irregular_absence_date",
"irregular_absence_counter",
"temporary_exempt_start_date",
"temporary_exempt_end_date",
"resigning",
"cooperator_id.subscribed_shift_ids",
)
def _compute_status(self): def _compute_status(self):
update = int(self.env['ir.config_parameter'].sudo().get_param('always_update', False))
update = int(
self.env["ir.config_parameter"]
.sudo()
.get_param("always_update", False)
)
for rec in self: for rec in self:
if update or not rec.today: if update or not rec.today:
rec.status = 'ok'
rec.status = "ok"
continue continue
if rec.resigning: if rec.resigning:
rec.status = 'resigning'
rec.status = "resigning"
continue continue
if rec.working_mode == 'regular':
if rec.working_mode == "regular":
rec.status = rec._get_regular_status() rec.status = rec._get_regular_status()
elif rec.working_mode == 'irregular':
elif rec.working_mode == "irregular":
rec.status = rec._get_irregular_status() rec.status = rec._get_irregular_status()
elif rec.working_mode == 'exempt':
rec.status = 'ok'
elif rec.working_mode == "exempt":
rec.status = "ok"
_sql_constraints = [ _sql_constraints = [
('cooperator_uniq', 'unique (cooperator_id)', _('You can only set one cooperator status per cooperator')),
(
"cooperator_uniq",
"unique (cooperator_id)",
_("You can only set one cooperator status per cooperator"),
)
] ]
@api.constrains("working_mode", "irregular_start_date") @api.constrains("working_mode", "irregular_start_date")
def _constrains_irregular_start_date(self): def _constrains_irregular_start_date(self):
if self.working_mode == "irregular" and not self.irregular_start_date: if self.working_mode == "irregular" and not self.irregular_start_date:
raise UserError(_("Irregular workers must have an irregular start date."))
raise UserError(
_("Irregular workers must have an irregular start date.")
)
@api.multi @api.multi
def write(self, vals): def write(self, vals):
""" """
Overwrite write to historize the change Overwrite write to historize the change
""" """
for field in ['sr', 'sc', 'time_extension', 'extension_start_time', 'alert_start_time', 'unsubscribed']:
for field in [
"sr",
"sc",
"time_extension",
"extension_start_time",
"alert_start_time",
"unsubscribed",
]:
if not field in vals: if not field in vals:
continue continue
for rec in self: for rec in self:
data = { data = {
'status_id': rec.id,
'cooperator_id': rec.cooperator_id.id,
'type': 'counter',
'user_id': self.env.context.get('real_uid', self.env.uid),
"status_id": rec.id,
"cooperator_id": rec.cooperator_id.id,
"type": "counter",
"user_id": self.env.context.get("real_uid", self.env.uid),
} }
if vals.get(field, rec[field]) != rec[field]: if vals.get(field, rec[field]) != rec[field]:
data['change'] = '%s: %s -> %s' % (field.upper(), rec[field], vals.get(field))
self.env['cooperative.status.history'].sudo().create(data)
data["change"] = "{}: {} -> {}".format(
field.upper(),
rec[field],
vals.get(field),
)
self.env["cooperative.status.history"].sudo().create(data)
return super(CooperativeStatus, self).write(vals) return super(CooperativeStatus, self).write(vals)
@api.multi @api.multi
@ -152,28 +213,42 @@ class CooperativeStatus(models.Model):
Overwrite write to historize the change of status Overwrite write to historize the change of status
and make action on status change and make action on status change
""" """
if 'status' in vals:
self._cr.execute('select id, status, sr, sc from "%s" where id in %%s' % self._table, (self._ids,))
if "status" in vals:
self._cr.execute(
'select id, status, sr, sc from "%s" where id in %%s'
% self._table,
(self._ids,),
)
result = self._cr.dictfetchall() result = self._cr.dictfetchall()
old_status_per_id = {r['id'] : r for r in result}
old_status_per_id = {r["id"]: r for r in result}
for rec in self: for rec in self:
if old_status_per_id[rec.id]['status'] != vals['status']:
if old_status_per_id[rec.id]["status"] != vals["status"]:
data = { data = {
'status_id': rec.id,
'cooperator_id': rec.cooperator_id.id,
'type': 'status',
'change': "STATUS: %s -> %s" % (old_status_per_id[rec.id]['status'], vals['status']),
'user_id': self.env.context.get('real_uid', self.env.uid),
"status_id": rec.id,
"cooperator_id": rec.cooperator_id.id,
"type": "status",
"change": "STATUS: %s -> %s"
% (
old_status_per_id[rec.id]["status"],
vals["status"],
),
"user_id": self.env.context.get(
"real_uid", self.env.uid
),
} }
self.env['cooperative.status.history'].sudo().create(data)
rec._state_change(vals['status'])
self.env["cooperative.status.history"].sudo().create(data)
rec._state_change(vals["status"])
return super(CooperativeStatus, self)._write(vals) return super(CooperativeStatus, self)._write(vals)
def get_status_value(self): def get_status_value(self):
""" """
Workararound to get translated selection value instead of key in mail template. Workararound to get translated selection value instead of key in mail template.
""" """
state_list = self.env["cooperative.status"]._fields['status']._description_selection(self.env)
state_list = (
self.env["cooperative.status"]
._fields["status"]
._description_selection(self.env)
)
return dict(state_list)[self.status] return dict(state_list)[self.status]
@api.model @api.model
@ -181,7 +256,7 @@ class CooperativeStatus(models.Model):
""" """
Method call by the cron to update store value base on the date Method call by the cron to update store value base on the date
""" """
self.search([]).write({'today': fields.Date.today()})
self.search([]).write({"today": fields.Date.today()})
@api.model @api.model
def _cron_compute_counter_irregular(self, today=False): def _cron_compute_counter_irregular(self, today=False):
@ -190,15 +265,21 @@ class CooperativeStatus(models.Model):
once per day once per day
""" """
today = today or fields.Date.today() today = today or fields.Date.today()
journal = self.env['beesdoo.shift.journal'].search([('date', '=', today)])
journal = self.env["beesdoo.shift.journal"].search(
[("date", "=", today)]
)
if not journal: if not journal:
journal = self.env['beesdoo.shift.journal'].create({'date': today})
journal = self.env["beesdoo.shift.journal"].create({"date": today})
domain = self._get_irregular_worker_domain(today=today) domain = self._get_irregular_worker_domain(today=today)
irregular = self.search(domain) irregular = self.search(domain)
for status in irregular: for status in irregular:
delta = (today - status.irregular_start_date).days delta = (today - status.irregular_start_date).days
if delta and delta % self._period == 0 and status not in journal.line_ids:
if (
delta
and delta % self._period == 0
and status not in journal.line_ids
):
status._change_irregular_counter() status._change_irregular_counter()
journal.line_ids |= status journal.line_ids |= status
@ -217,7 +298,7 @@ class CooperativeStatus(models.Model):
############################## ##############################
# Computed field section # # Computed field section #
############################## ##############################
@api.depends('today')
@api.depends("today")
def _compute_future_alert_date(self): def _compute_future_alert_date(self):
""" """
Compute date until the worker is up to date Compute date until the worker is up to date
@ -226,7 +307,7 @@ class CooperativeStatus(models.Model):
for rec in self: for rec in self:
rec.future_alert_date = False rec.future_alert_date = False
@api.depends('today')
@api.depends("today")
def _compute_next_countdown_date(self): def _compute_next_countdown_date(self):
""" """
Compute the following countdown date. This date is the date when Compute the following countdown date. This date is the date when
@ -242,7 +323,7 @@ class CooperativeStatus(models.Model):
return the list of status that give access return the list of status that give access
to active cooperator privilege to active cooperator privilege
""" """
return ['ok', 'alert', 'extension', 'exempted']
return ["ok", "alert", "extension", "exempted"]
##################################### #####################################
# Status Change implementation # # Status Change implementation #
@ -253,14 +334,14 @@ class CooperativeStatus(models.Model):
Return the value of the status Return the value of the status
for the regular worker for the regular worker
""" """
return 'ok'
return "ok"
def _get_irregular_status(self): def _get_irregular_status(self):
""" """
Return the value of the status Return the value of the status
for the irregular worker for the irregular worker
""" """
return 'ok'
return "ok"
def _state_change(self, new_state): def _state_change(self, new_state):
""" """
@ -275,7 +356,6 @@ class CooperativeStatus(models.Model):
""" """
pass pass
############################################### ###############################################
###### Irregular Cron implementation ########## ###### Irregular Cron implementation ##########
############################################### ###############################################
@ -286,7 +366,7 @@ class CooperativeStatus(models.Model):
of valid irregular worker that should of valid irregular worker that should
get their counter changed by the cron get their counter changed by the cron
""" """
return [(0, '=', 1)]
return [(0, "=", 1)]
def _change_irregular_counter(self): def _change_irregular_counter(self):
""" """
@ -297,22 +377,31 @@ class CooperativeStatus(models.Model):
""" """
pass pass
class ShiftCronJournal(models.Model): class ShiftCronJournal(models.Model):
_name = 'beesdoo.shift.journal'
_description = 'beesdoo.shift.journal'
_order = 'date desc'
_rec_name = 'date'
_name = "beesdoo.shift.journal"
_description = "beesdoo.shift.journal"
_order = "date desc"
_rec_name = "date"
date = fields.Date() date = fields.Date()
line_ids = fields.Many2many('cooperative.status')
line_ids = fields.Many2many("cooperative.status")
_sql_constraints = [ _sql_constraints = [
('one_entry_per_day', 'unique (date)', _('You can only create one journal per day')),
(
"one_entry_per_day",
"unique (date)",
_("You can only create one journal per day"),
)
] ]
@api.multi @api.multi
def run(self): def run(self):
self.ensure_one() self.ensure_one()
if not self.user_has_groups('beesdoo_shift.group_cooperative_admin'):
raise ValidationError(_("You don't have the access to perform this action"))
self.sudo().env['cooperative.status']._cron_compute_counter_irregular(today=self.date)
if not self.user_has_groups("beesdoo_shift.group_cooperative_admin"):
raise ValidationError(
_("You don't have the access to perform this action")
)
self.sudo().env["cooperative.status"]._cron_compute_counter_irregular(
today=self.date
)

225
beesdoo_shift/models/planning.py

@ -1,54 +1,67 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from pytz import timezone, UTC
import math import math
from datetime import datetime, time, timedelta from datetime import datetime, time, timedelta
from pytz import UTC, timezone
from odoo import _, api, fields, models
from odoo.exceptions import UserError
def float_to_time(f): def float_to_time(f):
decimal, integer = math.modf(f) decimal, integer = math.modf(f)
return "%s:%s" % (str(int(integer)).zfill(2), str(int(round(decimal * 60))).zfill(2))
return "{}:{}".format(
str(int(integer)).zfill(2),
str(int(round(decimal * 60))).zfill(2),
)
def floatime_to_hour_minute(f): def floatime_to_hour_minute(f):
decimal, integer = math.modf(f) decimal, integer = math.modf(f)
return int(integer), int(round(decimal * 60)) return int(integer), int(round(decimal * 60))
def get_first_day_of_week(): def get_first_day_of_week():
today = datetime.now() today = datetime.now()
return (datetime.now() - timedelta(days=today.weekday())).date() return (datetime.now() - timedelta(days=today.weekday())).date()
class TaskType(models.Model): class TaskType(models.Model):
_name = 'beesdoo.shift.type'
_description = 'beesdoo.shift.type'
_name = "beesdoo.shift.type"
_description = "beesdoo.shift.type"
name = fields.Char() name = fields.Char()
description = fields.Text() description = fields.Text()
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
class DayNumber(models.Model): class DayNumber(models.Model):
_name = 'beesdoo.shift.daynumber'
_description = 'beesdoo.shift.daynumber'
_name = "beesdoo.shift.daynumber"
_description = "beesdoo.shift.daynumber"
_order = 'number asc'
_order = "number asc"
name = fields.Char() name = fields.Char()
number = fields.Integer("Day Number", help="From 1 to N, When you will instanciate your planning, Day 1 will be the start date of the instance, Day 2 the second, etc...")
number = fields.Integer(
"Day Number",
help="From 1 to N, When you will instanciate your planning, Day 1 will be the start date of the instance, Day 2 the second, etc...",
)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
class Planning(models.Model): class Planning(models.Model):
_name = 'beesdoo.shift.planning'
_description = 'beesdoo.shift.planning'
_order = 'sequence asc'
_name = "beesdoo.shift.planning"
_description = "beesdoo.shift.planning"
_order = "sequence asc"
sequence = fields.Integer() sequence = fields.Integer()
name = fields.Char() name = fields.Char()
task_template_ids = fields.One2many('beesdoo.shift.template', 'planning_id')
task_template_ids = fields.One2many(
"beesdoo.shift.template", "planning_id"
)
@api.model @api.model
def _get_next_planning(self, sequence): def _get_next_planning(self, sequence):
next_planning = self.search([('sequence', '>', sequence)])
next_planning = self.search([("sequence", ">", sequence)])
if not next_planning: if not next_planning:
return self.search([])[0] return self.search([])[0]
return next_planning[0] return next_planning[0]
@ -56,15 +69,15 @@ class Planning(models.Model):
@api.multi @api.multi
def _get_next_planning_date(self, date): def _get_next_planning_date(self, date):
self.ensure_one() self.ensure_one()
nb_of_day = max(self.task_template_ids.mapped('day_nb_id.number'))
nb_of_day = max(self.task_template_ids.mapped("day_nb_id.number"))
return date + timedelta(days=nb_of_day) return date + timedelta(days=nb_of_day)
@api.model @api.model
def _generate_next_planning(self): def _generate_next_planning(self):
config = self.env['ir.config_parameter'].sudo()
last_seq = int(config.get_param('last_planning_seq', 0))
config = self.env["ir.config_parameter"].sudo()
last_seq = int(config.get_param("last_planning_seq", 0))
date = fields.Date.from_string( date = fields.Date.from_string(
config.get_param('next_planning_date', 0)
config.get_param("next_planning_date", 0)
) )
planning = self._get_next_planning(last_seq) planning = self._get_next_planning(last_seq)
@ -72,49 +85,70 @@ class Planning(models.Model):
planning.task_template_ids._generate_task_day() planning.task_template_ids._generate_task_day()
next_date = planning._get_next_planning_date(date) next_date = planning._get_next_planning_date(date)
config.set_param('last_planning_seq', planning.sequence)
config.set_param('next_planning_date', next_date)
config.set_param("last_planning_seq", planning.sequence)
config.set_param("next_planning_date", next_date)
class TaskTemplate(models.Model): class TaskTemplate(models.Model):
_name = 'beesdoo.shift.template'
_description = 'beesdoo.shift.template'
_order = 'start_time'
_name = "beesdoo.shift.template"
_description = "beesdoo.shift.template"
_order = "start_time"
name = fields.Char(required=True) name = fields.Char(required=True)
planning_id = fields.Many2one('beesdoo.shift.planning', required=True)
day_nb_id = fields.Many2one('beesdoo.shift.daynumber', string='Day', required=True)
task_type_id = fields.Many2one('beesdoo.shift.type', string="Type")
# attendance_sheet_id = fields.Many2one('beesdoo.shift.sheet', string="Attendance Sheet") FIXME removed because beesdoo.shift.sheet is from another module.
planning_id = fields.Many2one("beesdoo.shift.planning", required=True)
day_nb_id = fields.Many2one(
"beesdoo.shift.daynumber", string="Day", required=True
)
task_type_id = fields.Many2one("beesdoo.shift.type", string="Type")
# attendance_sheet_id = fields.Many2one('beesdoo.shift.sheet', string="Attendance Sheet") FIXME removed because beesdoo.shift.sheet is from another module.
start_time = fields.Float(required=True) start_time = fields.Float(required=True)
end_time = fields.Float(required=True) end_time = fields.Float(required=True)
super_coop_id = fields.Many2one('res.users', string="Super Cooperative", domain=[('partner_id.super', '=', True)])
super_coop_id = fields.Many2one(
"res.users",
string="Super Cooperative",
domain=[("partner_id.super", "=", True)],
)
duration = fields.Float(help="Duration in Hour") duration = fields.Float(help="Duration in Hour")
worker_nb = fields.Integer(string="Number of worker", help="Max number of worker for this task", default=1)
worker_ids = fields.Many2many('res.partner', string="Recurrent worker assigned", domain=[('is_worker', '=', True)])
remaining_worker = fields.Integer(compute="_get_remaining", store=True, string="Remaining Place")
worker_nb = fields.Integer(
string="Number of worker",
help="Max number of worker for this task",
default=1,
)
worker_ids = fields.Many2many(
"res.partner",
string="Recurrent worker assigned",
domain=[("is_worker", "=", True)],
)
remaining_worker = fields.Integer(
compute="_get_remaining", store=True, string="Remaining Place"
)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
#For Kanban View Only
color = fields.Integer('Color Index')
# For Kanban View Only
color = fields.Integer("Color Index")
worker_name = fields.Char(compute="_get_worker_name") worker_name = fields.Char(compute="_get_worker_name")
#For calendar View
start_date = fields.Datetime(compute="_get_fake_date", search="_dummy_search")
end_date = fields.Datetime(compute="_get_fake_date", search="_dummy_search")
# For calendar View
start_date = fields.Datetime(
compute="_get_fake_date", search="_dummy_search"
)
end_date = fields.Datetime(
compute="_get_fake_date", search="_dummy_search"
)
def _get_utc_date(self, day, hour, minute): def _get_utc_date(self, day, hour, minute):
"""Combine day number, hours and minutes to save """Combine day number, hours and minutes to save
corresponding UTC datetime in database. corresponding UTC datetime in database.
""" """
context_tz = timezone(self._context.get('tz') or self.env.user.tz)
context_tz = timezone(self._context.get("tz") or self.env.user.tz)
day_local_time = datetime.combine(day, time(hour=hour, minute=minute)) day_local_time = datetime.combine(day, time(hour=hour, minute=minute))
day_local_time = context_tz.localize(day_local_time) day_local_time = context_tz.localize(day_local_time)
day_utc_time = day_local_time.astimezone(UTC) day_utc_time = day_local_time.astimezone(UTC)
# Return naïve datetime so as to be saved in database # Return naïve datetime so as to be saved in database
return day_utc_time.replace(tzinfo=None)
return day_utc_time.replace(tzinfo=None)
@api.depends('start_time', 'end_time')
@api.depends("start_time", "end_time")
def _get_fake_date(self): def _get_fake_date(self):
today = self._context.get('visualize_date', get_first_day_of_week())
today = self._context.get("visualize_date", get_first_day_of_week())
for rec in self: for rec in self:
# Find the day of this task template 'rec'. # Find the day of this task template 'rec'.
day = today + timedelta(days=rec.day_nb_id.number - 1) day = today + timedelta(days=rec.day_nb_id.number - 1)
@ -128,84 +162,109 @@ class TaskTemplate(models.Model):
def _dummy_search(self, operator, value): def _dummy_search(self, operator, value):
return [] return []
@api.depends('worker_ids', 'worker_nb')
@api.depends("worker_ids", "worker_nb")
def _get_remaining(self): def _get_remaining(self):
for rec in self: for rec in self:
rec.remaining_worker = rec.worker_nb - len(rec.worker_ids)
rec.remaining_worker = rec.worker_nb - len(rec.worker_ids)
@api.depends("worker_ids") @api.depends("worker_ids")
def _get_worker_name(self): def _get_worker_name(self):
for rec in self: for rec in self:
rec.worker_name = ','.join(rec.worker_ids.mapped('display_name'))
rec.worker_name = ",".join(rec.worker_ids.mapped("display_name"))
@api.constrains('worker_nb', 'worker_ids')
@api.constrains("worker_nb", "worker_ids")
def _nb_worker_max(self): def _nb_worker_max(self):
for rec in self: for rec in self:
if len(rec.worker_ids) > rec.worker_nb: if len(rec.worker_ids) > rec.worker_nb:
raise UserError(_('You cannot assign more workers than the maximal number defined on template.'))
raise UserError(
_(
"You cannot assign more workers than the maximal number defined on template."
)
)
@api.onchange('start_time', 'end_time')
@api.onchange("start_time", "end_time")
def _get_duration(self): def _get_duration(self):
if self.start_time and self.end_time: if self.start_time and self.end_time:
self.duration = self.end_time - self.start_time self.duration = self.end_time - self.start_time
@api.onchange('duration')
@api.onchange("duration")
def _set_duration(self): def _set_duration(self):
if self.start_time: if self.start_time:
self.end_time = self.start_time +self.duration
self.end_time = self.start_time + self.duration
def _generate_task_day(self): def _generate_task_day(self):
tasks = self.env['beesdoo.shift.shift']
tasks = self.env["beesdoo.shift.shift"]
for rec in self: for rec in self:
for i in range(0, rec.worker_nb): for i in range(0, rec.worker_nb):
worker_id = rec.worker_ids[i] if len(rec.worker_ids) > i else False
#remove worker in holiday and temporary exempted
worker_id = (
rec.worker_ids[i] if len(rec.worker_ids) > i else False
)
# remove worker in holiday and temporary exempted
if worker_id and worker_id.cooperative_status_ids: if worker_id and worker_id.cooperative_status_ids:
status = worker_id.cooperative_status_ids[0] status = worker_id.cooperative_status_ids[0]
if status.holiday_start_time and status.holiday_end_time and \
status.holiday_start_time <= rec.start_date.date() and status.holiday_end_time >= rec.end_date.date():
if (
status.holiday_start_time
and status.holiday_end_time
and status.holiday_start_time <= rec.start_date.date()
and status.holiday_end_time >= rec.end_date.date()
):
worker_id = False worker_id = False
if status.temporary_exempt_start_date and status.temporary_exempt_end_date and \
status.temporary_exempt_start_date <= rec.start_date.date() and status.temporary_exempt_end_date >= rec.end_date.date():
if (
status.temporary_exempt_start_date
and status.temporary_exempt_end_date
and status.temporary_exempt_start_date
<= rec.start_date.date()
and status.temporary_exempt_end_date
>= rec.end_date.date()
):
worker_id = False worker_id = False
tasks |= tasks.create({
'name' : "[%s] %s %s (%s - %s) [%s]" % (
rec.start_date.date(),
rec.planning_id.name,
rec.day_nb_id.name,
float_to_time(rec.start_time),
float_to_time(rec.end_time),
i,
),
'task_template_id' : rec.id,
'task_type_id' : rec.task_type_id.id,
'super_coop_id': rec.super_coop_id.id,
'worker_id' : worker_id and worker_id.id or False,
'is_regular': True if worker_id else False,
'start_time' : rec.start_date,
'end_time' : rec.end_date,
'state': 'open',
})
tasks |= tasks.create(
{
"name": "[%s] %s %s (%s - %s) [%s]"
% (
rec.start_date.date(),
rec.planning_id.name,
rec.day_nb_id.name,
float_to_time(rec.start_time),
float_to_time(rec.end_time),
i,
),
"task_template_id": rec.id,
"task_type_id": rec.task_type_id.id,
"super_coop_id": rec.super_coop_id.id,
"worker_id": worker_id and worker_id.id or False,
"is_regular": True if worker_id else False,
"start_time": rec.start_date,
"end_time": rec.end_date,
"state": "open",
}
)
return tasks return tasks
@api.onchange('worker_ids')
@api.onchange("worker_ids")
def check_for_multiple_shifts(self): def check_for_multiple_shifts(self):
original_ids = {worker.id for worker in self._origin.worker_ids} original_ids = {worker.id for worker in self._origin.worker_ids}
warnings = [] warnings = []
for worker in self.worker_ids: for worker in self.worker_ids:
if worker.id not in original_ids: if worker.id not in original_ids:
shifts = [shift.name for shift in worker.subscribed_shift_ids if shift.id != self.id]
shifts = [
shift.name
for shift in worker.subscribed_shift_ids
if shift.id != self.id
]
if shifts: if shifts:
warnings.append( warnings.append(
worker.name + _(' is already assigned to ') + ", ".join(shifts))
worker.name
+ _(" is already assigned to ")
+ ", ".join(shifts)
)
if warnings: if warnings:
return { return {
'warning': {
'title': _("Warning"),
'message': "\n".join(warnings)
"warning": {
"title": _("Warning"),
"message": "\n".join(warnings),
} }
} }

127
beesdoo_shift/models/res_partner.py

@ -1,8 +1,8 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError, UserError
from datetime import timedelta, datetime
import logging import logging
from datetime import datetime, timedelta
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
class ResPartner(models.Model): class ResPartner(models.Model):
@ -10,20 +10,59 @@ class ResPartner(models.Model):
One2many relationship with CooperativeStatus should One2many relationship with CooperativeStatus should
be replaced by inheritance. be replaced by inheritance.
""" """
_inherit = 'res.partner'
_inherit = "res.partner"
worker_store = fields.Boolean(default=False) worker_store = fields.Boolean(default=False)
is_worker = fields.Boolean(related="worker_store", string="Worker", readonly=False)
can_shop = fields.Boolean(string="Is worker allowed to shop?", compute="_compute_can_shop", store=True)
cooperative_status_ids = fields.One2many('cooperative.status', 'cooperator_id', readonly=True)
super = fields.Boolean(related='cooperative_status_ids.super', string="Super Cooperative", readonly=True, store=True)
info_session = fields.Boolean(related='cooperative_status_ids.info_session', string='Information Session ?', readonly=True, store=True)
info_session_date = fields.Date(related='cooperative_status_ids.info_session_date', string='Information Session Date', readonly=True, store=True)
working_mode = fields.Selection(related='cooperative_status_ids.working_mode', readonly=True, store=True)
exempt_reason_id = fields.Many2one(related='cooperative_status_ids.exempt_reason_id', readonly=True, store=True)
state = fields.Selection(related='cooperative_status_ids.status', readonly=True, store=True)
extension_start_time = fields.Date(related='cooperative_status_ids.extension_start_time', string="Extension Start Day", readonly=True, store=True)
subscribed_shift_ids = fields.Many2many('beesdoo.shift.template')
is_worker = fields.Boolean(
related="worker_store", string="Worker", readonly=False
)
can_shop = fields.Boolean(
string="Is worker allowed to shop?",
compute="_compute_can_shop",
store=True,
)
cooperative_status_ids = fields.One2many(
"cooperative.status", "cooperator_id", readonly=True
)
super = fields.Boolean(
related="cooperative_status_ids.super",
string="Super Cooperative",
readonly=True,
store=True,
)
info_session = fields.Boolean(
related="cooperative_status_ids.info_session",
string="Information Session ?",
readonly=True,
store=True,
)
info_session_date = fields.Date(
related="cooperative_status_ids.info_session_date",
string="Information Session Date",
readonly=True,
store=True,
)
working_mode = fields.Selection(
related="cooperative_status_ids.working_mode",
readonly=True,
store=True,
)
exempt_reason_id = fields.Many2one(
related="cooperative_status_ids.exempt_reason_id",
readonly=True,
store=True,
)
state = fields.Selection(
related="cooperative_status_ids.status", readonly=True, store=True
)
extension_start_time = fields.Date(
related="cooperative_status_ids.extension_start_time",
string="Extension Start Day",
readonly=True,
store=True,
)
subscribed_shift_ids = fields.Many2many("beesdoo.shift.template")
@api.depends("cooperative_status_ids") @api.depends("cooperative_status_ids")
def _compute_can_shop(self): def _compute_can_shop(self):
@ -41,58 +80,58 @@ class ResPartner(models.Model):
@api.multi @api.multi
def coop_subscribe(self): def coop_subscribe(self):
return { return {
'name': _('Subscribe Cooperator'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'beesdoo.shift.subscribe',
'target': 'new',
"name": _("Subscribe Cooperator"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "form",
"res_model": "beesdoo.shift.subscribe",
"target": "new",
} }
@api.multi @api.multi
def coop_unsubscribe(self): def coop_unsubscribe(self):
res = self.coop_subscribe() res = self.coop_subscribe()
res['context'] = {'default_unsubscribed': True}
res["context"] = {"default_unsubscribed": True}
return res return res
@api.multi @api.multi
def manual_extension(self): def manual_extension(self):
return { return {
'name': _('Manual Extension'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'beesdoo.shift.extension',
'target': 'new',
"name": _("Manual Extension"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "form",
"res_model": "beesdoo.shift.extension",
"target": "new",
} }
@api.multi @api.multi
def auto_extension(self): def auto_extension(self):
res = self.manual_extension() res = self.manual_extension()
res['context'] = {'default_auto': True}
res['name'] = _('Trigger Grace Delay')
res["context"] = {"default_auto": True}
res["name"] = _("Trigger Grace Delay")
return res return res
@api.multi @api.multi
def register_holiday(self): def register_holiday(self):
return { return {
'name': _('Register Holiday'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'beesdoo.shift.holiday',
'target': 'new',
"name": _("Register Holiday"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "form",
"res_model": "beesdoo.shift.holiday",
"target": "new",
} }
@api.multi @api.multi
def temporary_exempt(self): def temporary_exempt(self):
return { return {
'name': _('Temporary Exemption'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'beesdoo.shift.temporary_exemption',
'target': 'new',
"name": _("Temporary Exemption"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "form",
"res_model": "beesdoo.shift.temporary_exemption",
"target": "new",
} }
#TODO access right + vue on res.partner
# TODO access right + vue on res.partner

236
beesdoo_shift/models/task.py

@ -5,11 +5,10 @@ from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
class Task(models.Model): class Task(models.Model):
_name = 'beesdoo.shift.shift'
_name = "beesdoo.shift.shift"
_inherit = ['mail.thread']
_inherit = ["mail.thread"]
_order = "start_time asc" _order = "start_time asc"
@ -20,11 +19,11 @@ class Task(models.Model):
################################## ##################################
def _get_selection_status(self): def _get_selection_status(self):
return [ return [
("open","Confirmed"),
("done","Attended"),
("absent","Absent"),
("excused","Excused"),
("cancel","Cancelled")
("open", "Confirmed"),
("done", "Attended"),
("absent", "Absent"),
("excused", "Excused"),
("cancel", "Cancelled"),
] ]
def _get_color_mapping(self, state): def _get_color_mapping(self, state):
@ -40,40 +39,57 @@ class Task(models.Model):
def _get_final_state(self): def _get_final_state(self):
return ["done", "absent", "excused"] return ["done", "absent", "excused"]
name = fields.Char(track_visibility='always')
task_template_id = fields.Many2one('beesdoo.shift.template')
planning_id = fields.Many2one(related='task_template_id.planning_id', store=True)
task_type_id = fields.Many2one('beesdoo.shift.type', string="Task Type")
worker_id = fields.Many2one('res.partner', track_visibility='onchange',
domain=[
('is_worker', '=', True),
('working_mode', 'in', ('regular', 'irregular')),
('state', 'not in', ('unsubscribed', 'resigning')),
])
start_time = fields.Datetime(track_visibility='always', index=True, required=True)
end_time = fields.Datetime(track_visibility='always', required=True)
state = fields.Selection(selection=_get_selection_status,
name = fields.Char(track_visibility="always")
task_template_id = fields.Many2one("beesdoo.shift.template")
planning_id = fields.Many2one(
related="task_template_id.planning_id", store=True
)
task_type_id = fields.Many2one("beesdoo.shift.type", string="Task Type")
worker_id = fields.Many2one(
"res.partner",
track_visibility="onchange",
domain=[
("is_worker", "=", True),
("working_mode", "in", ("regular", "irregular")),
("state", "not in", ("unsubscribed", "resigning")),
],
)
start_time = fields.Datetime(
track_visibility="always", index=True, required=True
)
end_time = fields.Datetime(track_visibility="always", required=True)
state = fields.Selection(
selection=_get_selection_status,
default="open", default="open",
required=True, required=True,
track_visibility='onchange',
group_expand='_expand_states'
track_visibility="onchange",
group_expand="_expand_states",
) )
color = fields.Integer(compute="_compute_color") color = fields.Integer(compute="_compute_color")
super_coop_id = fields.Many2one('res.users', string="Super Cooperative", domain=[('partner_id.super', '=', True)], track_visibility='onchange')
super_coop_id = fields.Many2one(
"res.users",
string="Super Cooperative",
domain=[("partner_id.super", "=", True)],
track_visibility="onchange",
)
is_regular = fields.Boolean(default=False, string="Regular shift") is_regular = fields.Boolean(default=False, string="Regular shift")
is_compensation = fields.Boolean(default=False, string="Compensation shift")
replaced_id = fields.Many2one('res.partner', track_visibility='onchange',
domain=[
('eater', '=', 'worker_eater'),
('working_mode', '=', 'regular'),
('state', 'not in', ('unsubscribed', 'resigning')),
])
is_compensation = fields.Boolean(
default=False, string="Compensation shift"
)
replaced_id = fields.Many2one(
"res.partner",
track_visibility="onchange",
domain=[
("eater", "=", "worker_eater"),
("working_mode", "=", "regular"),
("state", "not in", ("unsubscribed", "resigning")),
],
)
revert_info = fields.Text(copy=False) revert_info = fields.Text(copy=False)
working_mode = fields.Selection(related='worker_id.working_mode')
working_mode = fields.Selection(related="worker_id.working_mode")
def _expand_states(self, states, domain, order): def _expand_states(self, states, domain, order):
return [key for key, val in self._fields['state'].selection]
return [key for key, val in self._fields["state"].selection]
@api.depends("state") @api.depends("state")
def _compute_color(self): def _compute_color(self):
@ -85,8 +101,9 @@ class Task(models.Model):
Raise a validation error if the fields is_regular and Raise a validation error if the fields is_regular and
is_compensation are not properly set. is_compensation are not properly set.
""" """
if (task.is_regular == task.is_compensation
or not (task.is_regular or task.is_compensation)):
if task.is_regular == task.is_compensation or not (
task.is_regular or task.is_compensation
):
raise ValidationError( raise ValidationError(
"You must choose between Regular Shift or " "You must choose between Regular Shift or "
"Compensation Shift." "Compensation Shift."
@ -96,18 +113,20 @@ class Task(models.Model):
def _lock_future_task(self): def _lock_future_task(self):
if datetime.now() < self.start_time: if datetime.now() < self.start_time:
if self.state in self._get_final_state(): if self.state in self._get_final_state():
raise UserError(_(
"Shift state of a future shift "
"can't be set to 'present' or 'absent'."
))
@api.constrains('is_regular', 'is_compensation')
raise UserError(
_(
"Shift state of a future shift "
"can't be set to 'present' or 'absent'."
)
)
@api.constrains("is_regular", "is_compensation")
def _check_compensation(self): def _check_compensation(self):
for task in self: for task in self:
if task.working_mode == 'regular':
if task.working_mode == "regular":
self._compensation_validation(task) self._compensation_validation(task)
@api.constrains('worker_id')
@api.constrains("worker_id")
def _check_worker_id(self): def _check_worker_id(self):
""" """
When worker_id changes we need to check whether is_regular When worker_id changes we need to check whether is_regular
@ -117,29 +136,30 @@ class Task(models.Model):
False. False.
""" """
for task in self: for task in self:
if task.working_mode == 'regular':
if task.working_mode == "regular":
self._compensation_validation(task) self._compensation_validation(task)
else: else:
task.write({
'is_regular': False,
'is_compensation': False,
})
task.write({"is_regular": False, "is_compensation": False})
if task.worker_id: if task.worker_id:
if task.worker_id == task.replaced_id: if task.worker_id == task.replaced_id:
raise UserError("A worker cannot replace himself.") raise UserError("A worker cannot replace himself.")
def message_auto_subscribe(self, updated_fields, values=None): def message_auto_subscribe(self, updated_fields, values=None):
self._add_follower(values) self._add_follower(values)
return super(Task, self).message_auto_subscribe(updated_fields, values=values)
return super(Task, self).message_auto_subscribe(
updated_fields, values=values
)
def _add_follower(self, vals): def _add_follower(self, vals):
if vals.get('worker_id'):
worker = self.env['res.partner'].browse(vals['worker_id'])
if vals.get("worker_id"):
worker = self.env["res.partner"].browse(vals["worker_id"])
self.message_subscribe(partner_ids=worker.ids) self.message_subscribe(partner_ids=worker.ids)
#TODO button to replaced someone
# TODO button to replaced someone
@api.model @api.model
def unsubscribe_from_today(self, worker_ids, today=None, end_date=None, now=None):
def unsubscribe_from_today(
self, worker_ids, today=None, end_date=None, now=None
):
""" """
Unsubscribe workers from *worker_ids* from all shift that start *today* and later. Unsubscribe workers from *worker_ids* from all shift that start *today* and later.
If *end_date* is given, unsubscribe workers from shift between *today* and *end_date*. If *end_date* is given, unsubscribe workers from shift between *today* and *end_date*.
@ -152,31 +172,43 @@ class Task(models.Model):
""" """
if now: if now:
if not isinstance(now, datetime): if not isinstance(now, datetime):
raise UserError (_("'Now' must be a datetime."))
date_domain = [('start_time', '>', now)]
raise UserError(_("'Now' must be a datetime."))
date_domain = [("start_time", ">", now)]
else: else:
today = today or fields.Date.today() today = today or fields.Date.today()
today = datetime.combine(today, time()) today = datetime.combine(today, time())
date_domain = [('start_time', '>', today)]
date_domain = [("start_time", ">", today)]
if end_date: if end_date:
end_date = datetime.combine(end_date,time(hour=23, minute=59, second=59))
date_domain.append(('end_time', '<=', end_date))
end_date = datetime.combine(
end_date, time(hour=23, minute=59, second=59)
)
date_domain.append(("end_time", "<=", end_date))
to_unsubscribe = self.search([('worker_id', 'in', worker_ids)] + date_domain)
to_unsubscribe.write({'worker_id': False})
to_unsubscribe = self.search(
[("worker_id", "in", worker_ids)] + date_domain
)
to_unsubscribe.write({"worker_id": False})
# Remove worker, replaced_id and regular # Remove worker, replaced_id and regular
to_unsubscribe_replace = self.search([('replaced_id', 'in', worker_ids)] + date_domain)
to_unsubscribe_replace.write({'worker_id': False, 'replaced_id': False})
to_unsubscribe_replace = self.search(
[("replaced_id", "in", worker_ids)] + date_domain
)
to_unsubscribe_replace.write(
{"worker_id": False, "replaced_id": False}
)
# If worker is Super cooperator, remove it from planning # If worker is Super cooperator, remove it from planning
super_coop_ids = self.env['res.users'].search(
[('partner_id', 'in', worker_ids), ('super', '=', True)]).ids
super_coop_ids = (
self.env["res.users"]
.search([("partner_id", "in", worker_ids), ("super", "=", True)])
.ids
)
if super_coop_ids: if super_coop_ids:
to_unsubscribe_super_coop = self.search( to_unsubscribe_super_coop = self.search(
[('super_coop_id', 'in', super_coop_ids)] + date_domain)
to_unsubscribe_super_coop.write({'super_coop_id': False})
[("super_coop_id", "in", super_coop_ids)] + date_domain
)
to_unsubscribe_super_coop.write({"super_coop_id": False})
@api.multi @api.multi
def write(self, vals): def write(self, vals):
@ -187,56 +219,82 @@ class Task(models.Model):
Change the worker info Change the worker info
Compute state change for the new worker Compute state change for the new worker
""" """
if 'worker_id' in vals:
if "worker_id" in vals:
for rec in self: for rec in self:
if rec.worker_id.id != vals['worker_id']:
if rec.worker_id.id != vals["worker_id"]:
rec._revert() rec._revert()
# To satisfy the constrains on worker_id, it must be # To satisfy the constrains on worker_id, it must be
# accompanied by the change in is_regular and # accompanied by the change in is_regular and
# is_compensation field. # is_compensation field.
super(Task, rec).write({
'worker_id': vals['worker_id'],
'is_regular': vals.get('is_regular', rec.is_regular),
'is_compensation': vals.get('is_compensation',
rec.is_compensation),
})
super(Task, rec).write(
{
"worker_id": vals["worker_id"],
"is_regular": vals.get(
"is_regular", rec.is_regular
),
"is_compensation": vals.get(
"is_compensation", rec.is_compensation
),
}
)
rec._update_state(rec.state) rec._update_state(rec.state)
if 'state' in vals:
if "state" in vals:
for rec in self: for rec in self:
if vals['state'] != rec.state:
rec._update_state(vals['state'])
if vals["state"] != rec.state:
rec._update_state(vals["state"])
return super(Task, self).write(vals) return super(Task, self).write(vals)
def _set_revert_info(self, data, status): def _set_revert_info(self, data, status):
data_new = { data_new = {
'status_id': status.id,
'data' : {k: data.get(k, 0) * -1 for k in ['sr', 'sc', 'irregular_absence_counter']}
"status_id": status.id,
"data": {
k: data.get(k, 0) * -1
for k in ["sr", "sc", "irregular_absence_counter"]
},
} }
if data.get('irregular_absence_date'):
data_new['data']['irregular_absence_date'] = False
if data.get("irregular_absence_date"):
data_new["data"]["irregular_absence_date"] = False
self.write({'revert_info': json.dumps(data_new)})
self.write({"revert_info": json.dumps(data_new)})
def _revert(self): def _revert(self):
if not self.revert_info: if not self.revert_info:
return return
data = json.loads(self.revert_info) data = json.loads(self.revert_info)
self.env['cooperative.status'].browse(data['status_id']).sudo()._change_counter(data['data'])
self.env["cooperative.status"].browse(
data["status_id"]
).sudo()._change_counter(data["data"])
self.revert_info = False self.revert_info = False
def _update_state(self, new_state): def _update_state(self, new_state):
self.ensure_one() self.ensure_one()
self._revert() self._revert()
if not (self.worker_id or self.replaced_id) and new_state in self._get_final_state():
raise UserError(_("You cannot change to the status %s if no worker is defined for the shift") % new_state)
if (
not (self.worker_id or self.replaced_id)
and new_state in self._get_final_state()
):
raise UserError(
_(
"You cannot change to the status %s if no worker is defined for the shift"
)
% new_state
)
always_update = int(self.env['ir.config_parameter'].sudo().get_param('always_update', False))
always_update = int(
self.env["ir.config_parameter"]
.sudo()
.get_param("always_update", False)
)
if always_update or not (self.worker_id or self.replaced_id): if always_update or not (self.worker_id or self.replaced_id):
return return
if not (self.worker_id.working_mode in ['regular', 'irregular']):
raise UserError(_("Working mode is not properly defined. Please check if the worker is subscribed"))
if not (self.worker_id.working_mode in ["regular", "irregular"]):
raise UserError(
_(
"Working mode is not properly defined. Please check if the worker is subscribed"
)
)
data, status = self._get_counter_date_state_change(new_state) data, status = self._get_counter_date_state_change(new_state)
if status: if status:

2
beesdoo_shift/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Beescoop - Cellule IT
* Coop IT Easy SCRLfs

21
beesdoo_shift/wizard/assign_super_coop.py

@ -1,14 +1,23 @@
from odoo import models, fields, api, _
from odoo import _, api, fields, models
class AssignSuperCoop(models.TransientModel): class AssignSuperCoop(models.TransientModel):
_name = 'beesddoo.shift.assign_super_coop'
_description = 'beesddoo.shift.assign_super_coop'
_name = "beesddoo.shift.assign_super_coop"
_description = "beesddoo.shift.assign_super_coop"
super_coop_id = fields.Many2one('res.users', 'New Super Cooperative', required=True, domain=[('super', '=', True)])
shift_ids = fields.Many2many('beesdoo.shift.shift', readonly=True, default=lambda self: self._context.get('active_ids'))
super_coop_id = fields.Many2one(
"res.users",
"New Super Cooperative",
required=True,
domain=[("super", "=", True)],
)
shift_ids = fields.Many2many(
"beesdoo.shift.shift",
readonly=True,
default=lambda self: self._context.get("active_ids"),
)
@api.multi @api.multi
def write_super_coop(self): def write_super_coop(self):
self.ensure_one() self.ensure_one()
self.shift_ids.write({'super_coop_id' : self.super_coop_id.id})
self.shift_ids.write({"super_coop_id": self.super_coop_id.id})

72
beesdoo_shift/wizard/batch_template.py

@ -1,19 +1,30 @@
'''
"""
Created on 2 janv. 2017 Created on 2 janv. 2017
@author: Thibault Francois @author: Thibault Francois
'''
"""
from odoo import _, api, fields, models
from odoo import models, fields, api, _
class GenerateShiftTemplate(models.TransientModel): class GenerateShiftTemplate(models.TransientModel):
_name = 'beesddoo.shift.generate_shift_template'
_description = 'beesddoo.shift.generate_shift_template'
_name = "beesddoo.shift.generate_shift_template"
_description = "beesddoo.shift.generate_shift_template"
day_ids = fields.Many2many('beesdoo.shift.daynumber', relation='template_gen_day_number_rel', column1='wizard_id', column2='day_id')
planning_id = fields.Many2one('beesdoo.shift.planning', required=True)
type_id = fields.Many2one('beesdoo.shift.type', default=lambda self: self._context.get('active_id'))
line_ids = fields.One2many('beesddoo.shift.generate_shift_template.line', 'wizard_id')
day_ids = fields.Many2many(
"beesdoo.shift.daynumber",
relation="template_gen_day_number_rel",
column1="wizard_id",
column2="day_id",
)
planning_id = fields.Many2one("beesdoo.shift.planning", required=True)
type_id = fields.Many2one(
"beesdoo.shift.type",
default=lambda self: self._context.get("active_id"),
)
line_ids = fields.One2many(
"beesddoo.shift.generate_shift_template.line", "wizard_id"
)
@api.multi @api.multi
def generate(self): def generate(self):
@ -22,33 +33,36 @@ class GenerateShiftTemplate(models.TransientModel):
for day in self.day_ids: for day in self.day_ids:
for line in self.line_ids: for line in self.line_ids:
shift_template_data = { shift_template_data = {
'name': '%s' % self.type_id.name,
'planning_id': self.planning_id.id,
'task_type_id': self.type_id.id,
'day_nb_id': day.id,
'start_time': line.start_time,
'end_time': line.end_time,
'duration': line.end_time - line.start_time,
'worker_nb': line.worker_nb,
"name": "%s" % self.type_id.name,
"planning_id": self.planning_id.id,
"task_type_id": self.type_id.id,
"day_nb_id": day.id,
"start_time": line.start_time,
"end_time": line.end_time,
"duration": line.end_time - line.start_time,
"worker_nb": line.worker_nb,
} }
new_rec = self.env['beesdoo.shift.template'].create(shift_template_data)
new_rec = self.env["beesdoo.shift.template"].create(
shift_template_data
)
ids.append(new_rec.id) ids.append(new_rec.id)
return { return {
'name': _('Generated Shift Template'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'kanban,tree,form',
'res_model': 'beesdoo.shift.template',
'target': 'current',
'domain': [('id', 'in', ids)],
'context': {'group_by': 'day_nb_id'},
"name": _("Generated Shift Template"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "kanban,tree,form",
"res_model": "beesdoo.shift.template",
"target": "current",
"domain": [("id", "in", ids)],
"context": {"group_by": "day_nb_id"},
} }
class GenerateShiftTemplateLine(models.TransientModel): class GenerateShiftTemplateLine(models.TransientModel):
_name = 'beesddoo.shift.generate_shift_template.line'
_description = 'beesddoo.shift.generate_shift_template.line'
_name = "beesddoo.shift.generate_shift_template.line"
_description = "beesddoo.shift.generate_shift_template.line"
wizard_id = fields.Many2one('beesddoo.shift.generate_shift_template')
wizard_id = fields.Many2one("beesddoo.shift.generate_shift_template")
start_time = fields.Float(required=True) start_time = fields.Float(required=True)
end_time = fields.Float(required=True) end_time = fields.Float(required=True)
worker_nb = fields.Integer(default=1) worker_nb = fields.Integer(default=1)

64
beesdoo_shift/wizard/extension.py

@ -1,33 +1,63 @@
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
class Subscribe(models.TransientModel): class Subscribe(models.TransientModel):
_name = 'beesdoo.shift.extension'
_description = 'beesdoo.shift.extension'
_inherit = 'beesdoo.shift.action_mixin'
_name = "beesdoo.shift.extension"
_description = "beesdoo.shift.extension"
_inherit = "beesdoo.shift.action_mixin"
def _get_default_extension_delay(self): def _get_default_extension_delay(self):
return int(self.env['ir.config_parameter'].sudo().get_param('default_extension_delay', 28))
return int(
self.env["ir.config_parameter"]
.sudo()
.get_param("default_extension_delay", 28)
)
extension_start_date = fields.Date(string="Start date for the extension", default=fields.Date.today, readonly=True)
extension_start_date = fields.Date(
string="Start date for the extension",
default=fields.Date.today,
readonly=True,
)
auto = fields.Boolean("Auto Extension", default=False) auto = fields.Boolean("Auto Extension", default=False)
extension_days = fields.Integer(default=_get_default_extension_delay) extension_days = fields.Integer(default=_get_default_extension_delay)
@api.multi @api.multi
def auto_ext(self): def auto_ext(self):
self = self._check(group='beesdoo_shift.group_shift_attendance')
status_id = self.env['cooperative.status'].search([('cooperator_id', '=', self.cooperator_id.id)])
status_id.sudo().write({'extension_start_time': self.extension_start_date})
self = self._check(group="beesdoo_shift.group_shift_attendance")
status_id = self.env["cooperative.status"].search(
[("cooperator_id", "=", self.cooperator_id.id)]
)
status_id.sudo().write(
{"extension_start_time": self.extension_start_date}
)
@api.multi @api.multi
def extension(self): def extension(self):
self = self._check() #maybe a different group
grace_delay = int(self.env['ir.config_parameter'].sudo().get_param('default_grace_delay', 10))
status_id = self.env['cooperative.status'].search([('cooperator_id', '=', self.cooperator_id.id)])
self = self._check() # maybe a different group
grace_delay = int(
self.env["ir.config_parameter"]
.sudo()
.get_param("default_grace_delay", 10)
)
status_id = self.env["cooperative.status"].search(
[("cooperator_id", "=", self.cooperator_id.id)]
)
if not status_id.extension_start_time: if not status_id.extension_start_time:
raise UserError(_('You should not make a manual extension when the grace delay has not been triggered yet'))
today_delay = (status_id.today - status_id.extension_start_time).days - grace_delay
raise UserError(
_(
"You should not make a manual extension when the grace delay has not been triggered yet"
)
)
today_delay = (
status_id.today - status_id.extension_start_time
).days - grace_delay
if today_delay < 0: if today_delay < 0:
raise UserError(_('You should not start a manual extension during the grace delay'))
status_id.sudo().write({'time_extension': self.extension_days + today_delay})
raise UserError(
_(
"You should not start a manual extension during the grace delay"
)
)
status_id.sudo().write(
{"time_extension": self.extension_days + today_delay}
)

41
beesdoo_shift/wizard/holiday.py

@ -1,19 +1,40 @@
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class Subscribe(models.TransientModel): class Subscribe(models.TransientModel):
_name = 'beesdoo.shift.holiday'
_description = 'beesdoo.shift.holiday'
_inherit = 'beesdoo.shift.action_mixin'
_name = "beesdoo.shift.holiday"
_description = "beesdoo.shift.holiday"
_inherit = "beesdoo.shift.action_mixin"
holiday_start_day = fields.Date(string="Start date for the holiday", default=fields.Date.today)
holiday_start_day = fields.Date(
string="Start date for the holiday", default=fields.Date.today
)
holiday_end_day = fields.Date(string="End date for the holiday (included)") holiday_end_day = fields.Date(string="End date for the holiday (included)")
@api.multi @api.multi
def holidays(self): def holidays(self):
self = self._check() # maybe a different group self = self._check() # maybe a different group
status_id = self.env['cooperative.status'].search([('cooperator_id', '=', self.cooperator_id.id)])
if status_id.holiday_end_time and status_id.holiday_end_time >= status_id.today:
raise ValidationError(_("You cannot encode new holidays since the previous holidays encoded are not over yet"))
status_id.sudo().write({'holiday_start_time': self.holiday_start_day, 'holiday_end_time': self.holiday_end_day})
self.env['beesdoo.shift.shift'].sudo().unsubscribe_from_today([self.cooperator_id.id], today=self.holiday_start_day, end_date=self.holiday_end_day)
status_id = self.env["cooperative.status"].search(
[("cooperator_id", "=", self.cooperator_id.id)]
)
if (
status_id.holiday_end_time
and status_id.holiday_end_time >= status_id.today
):
raise ValidationError(
_(
"You cannot encode new holidays since the previous holidays encoded are not over yet"
)
)
status_id.sudo().write(
{
"holiday_start_time": self.holiday_start_day,
"holiday_end_time": self.holiday_end_day,
}
)
self.env["beesdoo.shift.shift"].sudo().unsubscribe_from_today(
[self.cooperator_id.id],
today=self.holiday_start_day,
end_date=self.holiday_end_day,
)

36
beesdoo_shift/wizard/instanciate_planning.py

@ -1,28 +1,34 @@
from odoo import models, fields, api, _
from odoo import _, api, fields, models
class InstanciatePlanning(models.TransientModel): class InstanciatePlanning(models.TransientModel):
_name = 'beesddoo.shift.generate_planning'
_description = 'beesddoo.shift.generate_planning'
_name = "beesddoo.shift.generate_planning"
_description = "beesddoo.shift.generate_planning"
def _get_planning(self): def _get_planning(self):
return self._context.get('active_id')
return self._context.get("active_id")
date_start = fields.Date("First Day of planning (should be monday)", required=True)
planning_id = fields.Many2one('beesdoo.shift.planning', readonly=True, default=_get_planning)
date_start = fields.Date(
"First Day of planning (should be monday)", required=True
)
planning_id = fields.Many2one(
"beesdoo.shift.planning", readonly=True, default=_get_planning
)
@api.multi @api.multi
def generate_task(self): def generate_task(self):
self.ensure_one() self.ensure_one()
self = self.with_context(visualize_date=self.date_start, tracking_disable=True)
self = self.with_context(
visualize_date=self.date_start, tracking_disable=True
)
shifts = self.planning_id.task_template_ids._generate_task_day() shifts = self.planning_id.task_template_ids._generate_task_day()
return { return {
'name': _('Generated Shift'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'kanban,calendar,tree,form,pivot',
'res_model': 'beesdoo.shift.shift',
'target': 'current',
'domain': [('id', 'in', shifts.ids)],
'context' : {'search_default_gb_day': 1}
"name": _("Generated Shift"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "kanban,calendar,tree,form,pivot",
"res_model": "beesdoo.shift.shift",
"target": "current",
"domain": [("id", "in", shifts.ids)],
"context": {"search_default_gb_day": 1},
} }

193
beesdoo_shift/wizard/subscribe.py

@ -1,86 +1,145 @@
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
class StatusActionMixin(models.AbstractModel): class StatusActionMixin(models.AbstractModel):
_name = "beesdoo.shift.action_mixin" _name = "beesdoo.shift.action_mixin"
_description = "beesdoo.shift.action_mixin" _description = "beesdoo.shift.action_mixin"
cooperator_id = fields.Many2one('res.partner', default=lambda self: self.env['res.partner'].browse(self._context.get('active_id')), required=True)
cooperator_id = fields.Many2one(
"res.partner",
default=lambda self: self.env["res.partner"].browse(
self._context.get("active_id")
),
required=True,
)
def _check(self, group='beesdoo_shift.group_shift_management'):
def _check(self, group="beesdoo_shift.group_shift_management"):
self.ensure_one() self.ensure_one()
if not self.env.user.has_group(group): if not self.env.user.has_group(group):
raise UserError(_("You don't have the required access for this operation."))
if self.cooperator_id == self.env.user.partner_id and not self.env.user.has_group('beesdoo_shift.group_cooperative_admin'):
raise UserError(
_("You don't have the required access for this operation.")
)
if (
self.cooperator_id == self.env.user.partner_id
and not self.env.user.has_group(
"beesdoo_shift.group_cooperative_admin"
)
):
raise UserError(_("You cannot perform this operation on yourself")) raise UserError(_("You cannot perform this operation on yourself"))
return self.with_context(real_uid=self._uid) return self.with_context(real_uid=self._uid)
class Subscribe(models.TransientModel): class Subscribe(models.TransientModel):
_name = 'beesdoo.shift.subscribe'
_description = 'beesdoo.shift.subscribe'
_inherit = 'beesdoo.shift.action_mixin'
_name = "beesdoo.shift.subscribe"
_description = "beesdoo.shift.subscribe"
_inherit = "beesdoo.shift.action_mixin"
def _get_date(self): def _get_date(self):
date = self.env['res.partner'].browse(self._context.get('active_id')).info_session_date
date = (
self.env["res.partner"]
.browse(self._context.get("active_id"))
.info_session_date
)
if not date: if not date:
return fields.Date.today() return fields.Date.today()
else: else:
return date return date
def _get_info_session_date(self): def _get_info_session_date(self):
date = (self.env['res.partner']
.browse(self._context.get('active_id'))
.info_session_date)
date = (
self.env["res.partner"]
.browse(self._context.get("active_id"))
.info_session_date
)
if date and self._get_info_session_followed(): if date and self._get_info_session_followed():
return date return date
else: else:
return False return False
def _get_info_session_followed(self): def _get_info_session_followed(self):
session_followed = (self.env['res.partner']
.browse(self._context.get('active_id'))
.info_session)
session_followed = (
self.env["res.partner"]
.browse(self._context.get("active_id"))
.info_session
)
return session_followed return session_followed
def _get_shift(self): def _get_shift(self):
shifts = self.env['res.partner'].browse(self._context.get('active_id')).subscribed_shift_ids
shifts = (
self.env["res.partner"]
.browse(self._context.get("active_id"))
.subscribed_shift_ids
)
if shifts: if shifts:
return shifts[0] return shifts[0]
return return
def _get_nb_shifts(self): def _get_nb_shifts(self):
return len(self.env['res.partner'].browse(self._context.get('active_id')).subscribed_shift_ids)
return len(
self.env["res.partner"]
.browse(self._context.get("active_id"))
.subscribed_shift_ids
)
def _get_super(self): def _get_super(self):
return self.env['res.partner'].browse(self._context.get('active_id')).super
return (
self.env["res.partner"]
.browse(self._context.get("active_id"))
.super
)
def _get_mode(self): def _get_mode(self):
return self.env['res.partner'].browse(self._context.get('active_id')).working_mode
return (
self.env["res.partner"]
.browse(self._context.get("active_id"))
.working_mode
)
def _get_reset_counter_default(self): def _get_reset_counter_default(self):
partner = self.env['res.partner'].browse(self._context.get('active_id'))
return partner.state == 'unsubscribed' and partner.working_mode == 'regular'
info_session = fields.Boolean(string="Followed an information session", default=_get_info_session_followed)
info_session_date = fields.Date(string="Date of information session", default=_get_info_session_date)
partner = self.env["res.partner"].browse(
self._context.get("active_id")
)
return (
partner.state == "unsubscribed"
and partner.working_mode == "regular"
)
info_session = fields.Boolean(
string="Followed an information session",
default=_get_info_session_followed,
)
info_session_date = fields.Date(
string="Date of information session", default=_get_info_session_date
)
super = fields.Boolean(string="Super Cooperator", default=_get_super) super = fields.Boolean(string="Super Cooperator", default=_get_super)
working_mode = fields.Selection( working_mode = fields.Selection(
[ [
('regular', 'Regular worker'),
('irregular', 'Irregular worker'),
('exempt', 'Exempted'),
], default=_get_mode
("regular", "Regular worker"),
("irregular", "Irregular worker"),
("exempt", "Exempted"),
],
default=_get_mode,
)
exempt_reason_id = fields.Many2one(
"cooperative.exempt.reason", "Exempt Reason"
)
shift_id = fields.Many2one("beesdoo.shift.template", default=_get_shift)
nb_shifts = fields.Integer(
string="Number of shifts", default=_get_nb_shifts
) )
exempt_reason_id = fields.Many2one('cooperative.exempt.reason', 'Exempt Reason')
shift_id = fields.Many2one('beesdoo.shift.template', default=_get_shift)
nb_shifts = fields.Integer(string='Number of shifts', default=_get_nb_shifts)
reset_counter = fields.Boolean(default=_get_reset_counter_default) reset_counter = fields.Boolean(default=_get_reset_counter_default)
reset_compensation_counter = fields.Boolean(default=False) reset_compensation_counter = fields.Boolean(default=False)
unsubscribed = fields.Boolean(default=False, string="Are you sure to remove this cooperator from his subscribed shift ?")
irregular_start_date = fields.Date(string="Start Date", default=fields.Date.today)
resigning = fields.Boolean(default=False, help="Want to leave the beescoop")
unsubscribed = fields.Boolean(
default=False,
string="Are you sure to remove this cooperator from his subscribed shift ?",
)
irregular_start_date = fields.Date(
string="Start Date", default=fields.Date.today
)
resigning = fields.Boolean(
default=False, help="Want to leave the beescoop"
)
@api.multi @api.multi
def unsubscribe(self): def unsubscribe(self):
@ -88,53 +147,57 @@ class Subscribe(models.TransientModel):
if not self.unsubscribed: if not self.unsubscribed:
return return
status_id = self.env['cooperative.status'].search([('cooperator_id', '=', self.cooperator_id.id)])
status_id = self.env["cooperative.status"].search(
[("cooperator_id", "=", self.cooperator_id.id)]
)
data = { data = {
'unsubscribed': True,
'cooperator_id': self.cooperator_id.id,
'resigning': self.resigning,
"unsubscribed": True,
"cooperator_id": self.cooperator_id.id,
"resigning": self.resigning,
} }
if status_id: if status_id:
status_id.sudo().write(data) status_id.sudo().write(data)
else: else:
self.env['cooperative.status'].sudo().create(data)
self.env["cooperative.status"].sudo().create(data)
@api.multi @api.multi
def subscribe(self): def subscribe(self):
self = self._check() self = self._check()
if self.shift_id and self.shift_id.remaining_worker <= 0: if self.shift_id and self.shift_id.remaining_worker <= 0:
raise UserError(_('There is no remaining space for this shift'))
raise UserError(_("There is no remaining space for this shift"))
if self.shift_id: if self.shift_id:
#Remove existing shift then subscribe to the new shift
self.cooperator_id.sudo().write({'subscribed_shift_ids' : [(6,0, [self.shift_id.id])]})
if self.working_mode != 'regular':
#Remove existing shift then subscribe to the new shift
self.cooperator_id.sudo().write({'subscribed_shift_ids': [(5,)]})
# Remove existing shift then subscribe to the new shift
self.cooperator_id.sudo().write(
{"subscribed_shift_ids": [(6, 0, [self.shift_id.id])]}
)
if self.working_mode != "regular":
# Remove existing shift then subscribe to the new shift
self.cooperator_id.sudo().write({"subscribed_shift_ids": [(5,)]})
data = { data = {
'info_session': self.info_session,
'info_session_date': self.info_session_date,
'working_mode': self.working_mode,
'exempt_reason_id' : self.exempt_reason_id.id,
'super': self.super,
'cooperator_id': self.cooperator_id.id,
'unsubscribed': False,
'irregular_start_date': self.irregular_start_date,
'irregular_absence_date': False,
'irregular_absence_counter': 0,
"info_session": self.info_session,
"info_session_date": self.info_session_date,
"working_mode": self.working_mode,
"exempt_reason_id": self.exempt_reason_id.id,
"super": self.super,
"cooperator_id": self.cooperator_id.id,
"unsubscribed": False,
"irregular_start_date": self.irregular_start_date,
"irregular_absence_date": False,
"irregular_absence_counter": 0,
} }
if self.reset_counter: if self.reset_counter:
data['sr'] = 0
data['extension_start_time'] = False
data['alert_start_time'] = False
data['time_extension'] = 0
data["sr"] = 0
data["extension_start_time"] = False
data["alert_start_time"] = False
data["time_extension"] = 0
if self.reset_compensation_counter: if self.reset_compensation_counter:
data['sc'] = 0
data["sc"] = 0
coop_status_obj = self.env['cooperative.status']
coop_status_obj = self.env["cooperative.status"]
status_id = coop_status_obj.search( status_id = coop_status_obj.search(
[('cooperator_id', '=', self.cooperator_id.id)])
[("cooperator_id", "=", self.cooperator_id.id)]
)
if status_id: if status_id:
status_id.sudo().write(data) status_id.sudo().write(data)
else: else:

50
beesdoo_shift/wizard/temporary_exemption.py

@ -1,24 +1,44 @@
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class TemporaryExemption(models.TransientModel): class TemporaryExemption(models.TransientModel):
_name = 'beesdoo.shift.temporary_exemption'
_description = 'beesdoo.shift.temporary_exemption'
_inherit = 'beesdoo.shift.action_mixin'
_name = "beesdoo.shift.temporary_exemption"
_description = "beesdoo.shift.temporary_exemption"
_inherit = "beesdoo.shift.action_mixin"
temporary_exempt_reason_id = fields.Many2one('cooperative.exempt.reason', 'Exempt Reason', required=True)
temporary_exempt_start_date = fields.Date(default=fields.Date.today, required=True)
temporary_exempt_reason_id = fields.Many2one(
"cooperative.exempt.reason", "Exempt Reason", required=True
)
temporary_exempt_start_date = fields.Date(
default=fields.Date.today, required=True
)
temporary_exempt_end_date = fields.Date(required=True) temporary_exempt_end_date = fields.Date(required=True)
@api.multi @api.multi
def exempt(self): def exempt(self):
self = self._check() # maybe a different group self = self._check() # maybe a different group
status_id = self.env['cooperative.status'].search([('cooperator_id', '=', self.cooperator_id.id)])
if status_id.temporary_exempt_end_date and status_id.temporary_exempt_end_date >= status_id.today:
raise ValidationError(_("You cannot encode new temporary exemptuon since the previous one are not over yet"))
status_id.sudo().write({
'temporary_exempt_start_date': self.temporary_exempt_start_date,
'temporary_exempt_end_date': self.temporary_exempt_end_date,
'temporary_exempt_reason_id': self.temporary_exempt_reason_id.id,
})
self.env['beesdoo.shift.shift'].sudo().unsubscribe_from_today([self.cooperator_id.id], today=self.temporary_exempt_start_date, end_date=self.temporary_exempt_end_date)
status_id = self.env["cooperative.status"].search(
[("cooperator_id", "=", self.cooperator_id.id)]
)
if (
status_id.temporary_exempt_end_date
and status_id.temporary_exempt_end_date >= status_id.today
):
raise ValidationError(
_(
"You cannot encode new temporary exemptuon since the previous one are not over yet"
)
)
status_id.sudo().write(
{
"temporary_exempt_start_date": self.temporary_exempt_start_date,
"temporary_exempt_end_date": self.temporary_exempt_end_date,
"temporary_exempt_reason_id": self.temporary_exempt_reason_id.id,
}
)
self.env["beesdoo.shift.shift"].sudo().unsubscribe_from_today(
[self.cooperator_id.id],
today=self.temporary_exempt_start_date,
end_date=self.temporary_exempt_end_date,
)

38
beesdoo_shift_attendance/__manifest__.py

@ -4,31 +4,25 @@
# this module can be splitted into a generic part, and a specific part # this module can be splitted into a generic part, and a specific part
# that implement the worker_status rules. # that implement the worker_status rules.
{ {
'name': "Beescoop Shift Attendance Sheet",
'summary': """
"name": "Beescoop Shift Attendance Sheet",
"summary": """
Volonteer Timetable Management Volonteer Timetable Management
Attendance Sheet for BEES coop""", Attendance Sheet for BEES coop""",
'description': """
"description": """
""", """,
'author': "Elouan Le Bars, Coop It Easy",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Cooperative management',
'version': '12.0.1.0.1',
'depends': [
'beesdoo_base',
'beesdoo_shift',
'beesdoo_worker_status',
'mail',
'barcodes',
"author": "Elouan Le Bars, Coop It Easy",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Cooperative management",
"version": "12.0.1.0.1",
"depends": [
"beesdoo_base",
"beesdoo_shift",
"beesdoo_worker_status",
"mail",
"barcodes",
], ],
'data': [
"data": [
"data/system_parameter.xml", "data/system_parameter.xml",
"data/cron.xml", "data/cron.xml",
"data/mail_template.xml", "data/mail_template.xml",
@ -39,7 +33,5 @@
"wizard/generate_missing_attendance_sheets.xml", "wizard/generate_missing_attendance_sheets.xml",
"views/attendance_sheet.xml", "views/attendance_sheet.xml",
], ],
'demo': [
"demo/users.xml",
]
"demo": ["demo/users.xml",],
} }

47
beesdoo_shift_attendance/models/attendance_sheet.py

@ -16,6 +16,7 @@ class AttendanceSheetShift(models.Model):
but create() method from res.partner raise error but create() method from res.partner raise error
when class is Abstract. when class is Abstract.
""" """
_name = "beesdoo.shift.sheet.shift" _name = "beesdoo.shift.sheet.shift"
_description = "Copy of an actual shift into an attendance sheet" _description = "Copy of an actual shift into an attendance sheet"
_order = "task_type_id, worker_name" _order = "task_type_id, worker_name"
@ -61,7 +62,9 @@ class AttendanceSheetShift(models.Model):
) )
worker_name = fields.Char(related="worker_id.name", store=True) worker_name = fields.Char(related="worker_id.name", store=True)
task_type_id = fields.Many2one( task_type_id = fields.Many2one(
"beesdoo.shift.type", string="Task Type", default=pre_filled_task_type_id
"beesdoo.shift.type",
string="Task Type",
default=pre_filled_task_type_id,
) )
working_mode = fields.Selection( working_mode = fields.Selection(
related="worker_id.working_mode", string="Working Mode" related="worker_id.working_mode", string="Working Mode"
@ -120,10 +123,7 @@ class AttendanceSheetShiftAdded(models.Model):
class AttendanceSheet(models.Model): class AttendanceSheet(models.Model):
_name = "beesdoo.shift.sheet" _name = "beesdoo.shift.sheet"
_inherit = [
"mail.thread",
"barcodes.barcode_events_mixin",
]
_inherit = ["mail.thread", "barcodes.barcode_events_mixin"]
_description = "Attendance sheet" _description = "Attendance sheet"
_order = "start_time" _order = "start_time"
@ -136,7 +136,7 @@ class AttendanceSheet(models.Model):
) )
active = fields.Boolean(string="Active", default=1) active = fields.Boolean(string="Active", default=1)
state = fields.Selection( state = fields.Selection(
[("not_validated", "Not Validated"), ("validated", "Validated"),],
[("not_validated", "Not Validated"), ("validated", "Validated")],
string="State", string="State",
readonly=True, readonly=True,
index=True, index=True,
@ -175,9 +175,7 @@ class AttendanceSheet(models.Model):
help="Indicative maximum number of workers.", help="Indicative maximum number of workers.",
) )
attended_worker_no = fields.Integer( attended_worker_no = fields.Integer(
string="Number of workers present",
default=0,
readonly=True,
string="Number of workers present", default=0, readonly=True
) )
notes = fields.Text( notes = fields.Text(
"Notes", "Notes",
@ -233,9 +231,7 @@ class AttendanceSheet(models.Model):
start_time = fields.Datetime.context_timestamp(rec, rec.start_time) start_time = fields.Datetime.context_timestamp(rec, rec.start_time)
end_time = fields.Datetime.context_timestamp(rec, rec.end_time) end_time = fields.Datetime.context_timestamp(rec, rec.end_time)
rec.time_slot = ( rec.time_slot = (
start_time.strftime("%H:%M")
+ "-"
+ end_time.strftime("%H:%M")
start_time.strftime("%H:%M") + "-" + end_time.strftime("%H:%M")
) )
@api.depends("start_time", "end_time", "week", "day_abbrevation") @api.depends("start_time", "end_time", "week", "day_abbrevation")
@ -319,10 +315,14 @@ class AttendanceSheet(models.Model):
) )
def on_barcode_scanned(self, barcode): def on_barcode_scanned(self, barcode):
if self.env.user.has_group("beesdoo_shift_attendance.group_shift_attendance"):
if self.env.user.has_group(
"beesdoo_shift_attendance.group_shift_attendance"
):
raise UserError( raise UserError(
_("You must be logged as 'Attendance Sheet Generic Access' "
" if you want to scan cards.")
_(
"You must be logged as 'Attendance Sheet Generic Access' "
" if you want to scan cards."
)
) )
if self.state == "validated": if self.state == "validated":
@ -370,7 +370,9 @@ class AttendanceSheet(models.Model):
) )
if worker.working_mode not in ("regular", "irregular"): if worker.working_mode not in ("regular", "irregular"):
raise UserError( raise UserError(
_("%s's working mode is %s and should be regular or irregular.")
_(
"%s's working mode is %s and should be regular or irregular."
)
% (worker.name, worker.working_mode) % (worker.name, worker.working_mode)
) )
@ -428,7 +430,11 @@ class AttendanceSheet(models.Model):
for task in tasks: for task in tasks:
# Only one shift is added if multiple similar exist # Only one shift is added if multiple similar exist
if task.worker_id and task.worker_id not in workers and (task.state != "cancel") :
if (
task.worker_id
and task.worker_id not in workers
and (task.state != "cancel")
):
expected_shift.create( expected_shift.create(
{ {
"attendance_sheet_id": new_sheet.id, "attendance_sheet_id": new_sheet.id,
@ -468,7 +474,8 @@ class AttendanceSheet(models.Model):
if expected_shift.state != "done": if expected_shift.state != "done":
mail_template = self.env.ref( mail_template = self.env.ref(
"beesdoo_shift_attendance.email_template_non_attendance", False
"beesdoo_shift_attendance.email_template_non_attendance",
False,
) )
mail_template.send_mail(expected_shift.task_id.id, True) mail_template.send_mail(expected_shift.task_id.id, True)
@ -609,7 +616,9 @@ class AttendanceSheet(models.Model):
sheets = self.env["beesdoo.shift.sheet"] sheets = self.env["beesdoo.shift.sheet"]
current_time = datetime.now() current_time = datetime.now()
generation_interval_setting = int( generation_interval_setting = int(
self.env["ir.config_parameter"].sudo().get_param(
self.env["ir.config_parameter"]
.sudo()
.get_param(
"beesdoo_shift_attendance.attendance_sheet_generation_interval" "beesdoo_shift_attendance.attendance_sheet_generation_interval"
) )
) )

4
beesdoo_shift_attendance/models/res_config_settings.py

@ -3,7 +3,7 @@
import ast import ast
from odoo import fields, models, api
from odoo import api, fields, models
class ResConfigSettings(models.TransientModel): class ResConfigSettings(models.TransientModel):
@ -46,6 +46,6 @@ class ResConfigSettings(models.TransientModel):
self.env["ir.config_parameter"].get_param( self.env["ir.config_parameter"].get_param(
"beesdoo_shift_attendance.pre_filled_task_type_id" "beesdoo_shift_attendance.pre_filled_task_type_id"
) )
),
)
) )
return res return res

22
beesdoo_shift_attendance/tests/test_beesdoo_shift.py

@ -20,8 +20,10 @@ class TestBeesdooShift(TransactionCase):
] ]
self.shift_expected_model = self.env["beesdoo.shift.sheet.expected"] self.shift_expected_model = self.env["beesdoo.shift.sheet.expected"]
self.shift_added_model = self.env["beesdoo.shift.sheet.added"] self.shift_added_model = self.env["beesdoo.shift.sheet.added"]
self.pre_filled_task_type_id = self.env["ir.config_parameter"].sudo().get_param(
"beesdoo_shift.pre_filled_task_type_id"
self.pre_filled_task_type_id = (
self.env["ir.config_parameter"]
.sudo()
.get_param("beesdoo_shift.pre_filled_task_type_id")
) )
self.current_time = datetime.now() self.current_time = datetime.now()
@ -67,12 +69,10 @@ class TestBeesdooShift(TransactionCase):
) )
self.task_template_1 = self.env.ref( self.task_template_1 = self.env.ref(
"beesdoo_worker_status"
".beesdoo_shift_task_template_1_demo"
"beesdoo_worker_status" ".beesdoo_shift_task_template_1_demo"
) )
self.task_template_2 = self.env.ref( self.task_template_2 = self.env.ref(
"beesdoo_worker_status"
".beesdoo_shift_task_template_2_demo"
"beesdoo_worker_status" ".beesdoo_shift_task_template_2_demo"
) )
# Set time in and out of generation interval parameter # Set time in and out of generation interval parameter
@ -207,9 +207,7 @@ class TestBeesdooShift(TransactionCase):
# Test consistency with actual shift for sheet 1 # Test consistency with actual shift for sheet 1
for shift in sheet_1.expected_shift_ids: for shift in sheet_1.expected_shift_ids:
self.assertEquals(shift.worker_id, shift.task_id.worker_id) self.assertEquals(shift.worker_id, shift.task_id.worker_id)
self.assertEquals(
shift.replaced_id, shift.task_id.replaced_id
)
self.assertEquals(shift.replaced_id, shift.task_id.replaced_id)
self.assertEqual(shift.task_type_id, shift.task_id.task_type_id) self.assertEqual(shift.task_type_id, shift.task_id.task_type_id)
self.assertEqual(shift.super_coop_id, shift.task_id.super_coop_id) self.assertEqual(shift.super_coop_id, shift.task_id.super_coop_id)
self.assertEqual(shift.working_mode, shift.task_id.working_mode) self.assertEqual(shift.working_mode, shift.task_id.working_mode)
@ -239,7 +237,7 @@ class TestBeesdooShift(TransactionCase):
self.attendance_sheet_model.sudo( self.attendance_sheet_model.sudo(
self.user_generic self.user_generic
)._generate_attendance_sheet() )._generate_attendance_sheet()
sheet_1 = self.search_sheets(self.start_in_1, self.end_in_1,)
sheet_1 = self.search_sheets(self.start_in_1, self.end_in_1)
sheet_1 = sheet_1.sudo(self.user_generic) sheet_1 = sheet_1.sudo(self.user_generic)
""" """
@ -352,7 +350,9 @@ class TestBeesdooShift(TransactionCase):
if waiting_time > 0: if waiting_time > 0:
with self.assertRaises(UserError) as econtext: with self.assertRaises(UserError) as econtext:
sheet_1.validate_with_checks() sheet_1.validate_with_checks()
self.assertIn("once the shifts have started", str(econtext.exception))
self.assertIn(
"once the shifts have started", str(econtext.exception)
)
time.sleep(waiting_time) time.sleep(waiting_time)
sheet_1.worker_nb_feedback = "enough" sheet_1.worker_nb_feedback = "enough"

8
beesdoo_shift_attendance/wizard/generate_missing_attendance_sheets.py

@ -1,8 +1,8 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
from datetime import datetime from datetime import datetime
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
class GenerateMissingAttendanceSheets(models.TransientModel): class GenerateMissingAttendanceSheets(models.TransientModel):
""" """
@ -34,7 +34,7 @@ class GenerateMissingAttendanceSheets(models.TransientModel):
start_time = task.start_time start_time = task.start_time
end_time = task.end_time end_time = task.end_time
sheet = sheets.search( sheet = sheets.search(
[("start_time", "=", start_time), ("end_time", "=", end_time),]
[("start_time", "=", start_time), ("end_time", "=", end_time)]
) )
if not sheet: if not sheet:

7
beesdoo_shift_attendance/wizard/validate_attendance_sheet.py

@ -1,4 +1,3 @@
import ast import ast
from odoo import _, api, exceptions, fields, models from odoo import _, api, exceptions, fields, models
@ -21,9 +20,9 @@ class ValidateAttendanceSheet(models.TransientModel):
def _get_card_support_setting(self): def _get_card_support_setting(self):
return ast.literal_eval( return ast.literal_eval(
self.env["ir.config_parameter"].sudo().get_param(
"beesdoo_shift_attendance.card_support"
)
self.env["ir.config_parameter"]
.sudo()
.get_param("beesdoo_shift_attendance.card_support")
) )
@api.multi @api.multi

10
beesdoo_stock/__manifest__.py

@ -4,17 +4,13 @@
{ {
"name": "BEES coop Stock", "name": "BEES coop Stock",
"version": "12.0.1.0.0", "version": "12.0.1.0.0",
"depends": [
'stock',
],
"depends": ["stock"],
"author": "Coop IT Easy SCRLfs", "author": "Coop IT Easy SCRLfs",
"license": "AGPL-3", "license": "AGPL-3",
"website": "www.coopiteasy.be", "website": "www.coopiteasy.be",
"description": """ "description": """
Enable action on multiple products of a stock receipt Enable action on multiple products of a stock receipt
""", """,
"data": [
'views/stock_view.xml',
],
'installable': True,
"data": ["views/stock_view.xml"],
"installable": True,
} }

30
beesdoo_stock/models/stock.py

@ -2,27 +2,27 @@ from odoo import api, fields, models
class StockPackOperation(models.Model): class StockPackOperation(models.Model):
_inherit = 'stock.picking'
_inherit = "stock.picking"
@api.multi @api.multi
def actions_on_articles(self): def actions_on_articles(self):
ids = self._ids ids = self._ids
context = self._context context = self._context
ctx = (context or {}).copy() ctx = (context or {}).copy()
ctx['articles'] = []
ctx["articles"] = []
for line in self.browse(ids).move_line_ids: for line in self.browse(ids).move_line_ids:
ctx['articles'].append(line.product_id.product_tmpl_id.id)
if ctx['articles']:
ctx["articles"].append(line.product_id.product_tmpl_id.id)
if ctx["articles"]:
return { return {
'name': 'Articles',
'view_type': 'list',
'view_mode': 'list',
'res_model': 'product.template',
'view_id': False,
'target': 'current',
'type': 'ir.actions.act_window',
'context': ctx,
'nodestroy': True,
'res_id': ctx['articles'],
'domain': [('id', 'in', ctx['articles'])],
"name": "Articles",
"view_type": "list",
"view_mode": "list",
"res_model": "product.template",
"view_id": False,
"target": "current",
"type": "ir.actions.act_window",
"context": ctx,
"nodestroy": True,
"res_id": ctx["articles"],
"domain": [("id", "in", ctx["articles"])],
} }

2
beesdoo_stock_coverage/models/product_template.py

@ -2,7 +2,7 @@
# Robin Keunen <robin@coopiteasy.be> # Robin Keunen <robin@coopiteasy.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError

1
beesdoo_stock_coverage/readme/CONTRIBUTORS.rst

@ -1,2 +1 @@
* Robin Keunen <robin@keunen.net> * Robin Keunen <robin@keunen.net>

5
beesdoo_stock_coverage/tests/test_stock_coverage.py

@ -3,10 +3,11 @@
# @author: Robin Keunen <robin@coopiteasy.be> # @author: Robin Keunen <robin@coopiteasy.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import timedelta
from odoo import fields from odoo import fields
from odoo.tools import float_compare
from odoo.tests.common import TransactionCase from odoo.tests.common import TransactionCase
from datetime import timedelta
from odoo.tools import float_compare
class TestModule(TransactionCase): class TestModule(TransactionCase):

31
beesdoo_website_eater/__manifest__.py

@ -2,28 +2,17 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'BEES coop Website Eater',
'summary': """
"name": "BEES coop Website Eater",
"summary": """
Show the eaters of a cooperator in the website portal. Show the eaters of a cooperator in the website portal.
""", """,
'description': """
"description": """
""", """,
'author': 'Rémy Taymans',
'license': 'AGPL-3',
'version': '12.0.1.0.0',
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Website',
'depends': [
'website',
'portal',
'beesdoo_base',
],
'data': [
'views/beesdoo_website_eater_templates.xml',
]
"author": "Rémy Taymans",
"license": "AGPL-3",
"version": "12.0.1.0.0",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Website",
"depends": ["website", "portal", "beesdoo_base"],
"data": ["views/beesdoo_website_eater_templates.xml"],
} }

10
beesdoo_website_eater/controllers/main.py

@ -8,12 +8,10 @@ from odoo.addons.portal.controllers.portal import CustomerPortal
class EaterWebsiteAccount(CustomerPortal): class EaterWebsiteAccount(CustomerPortal):
def _prepare_portal_layout_values(self): def _prepare_portal_layout_values(self):
values = super(EaterWebsiteAccount,
self)._prepare_portal_layout_values()
values = super(
EaterWebsiteAccount, self
)._prepare_portal_layout_values()
partner = request.env.user.partner_id.commercial_partner_id partner = request.env.user.partner_id.commercial_partner_id
values.update({
'eaters': partner.child_eater_ids,
})
values.update({"eaters": partner.child_eater_ids})
return values return values

22
beesdoo_website_posorder_amount/controllers/main.py

@ -2,22 +2,30 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.addons.portal.controllers.portal import CustomerPortal
from odoo.http import request from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal
class PortalPosOrderAmount(CustomerPortal): class PortalPosOrderAmount(CustomerPortal):
def _prepare_portal_layout_values(self): def _prepare_portal_layout_values(self):
values = super( values = super(
PortalPosOrderAmount, self PortalPosOrderAmount, self
)._prepare_portal_layout_values() )._prepare_portal_layout_values()
user = request.env.user user = request.env.user
owned_posorder = request.env["pos.order"].sudo().search(
[
("partner_id", "=", user.partner_id.commercial_partner_id.id),
("state", "!=", "cancel"),
]
owned_posorder = (
request.env["pos.order"]
.sudo()
.search(
[
(
"partner_id",
"=",
user.partner_id.commercial_partner_id.id,
),
("state", "!=", "cancel"),
]
)
) )
values["posorder_amount"] = sum( values["posorder_amount"] = sum(
po.amount_total for po in owned_posorder po.amount_total for po in owned_posorder

6
beesdoo_website_shift/__manifest__.py

@ -3,13 +3,13 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'BEES coop Website Shift',
'summary': """
"name": "BEES coop Website Shift",
"summary": """
Show available shifts for regular and irregular workers on the Show available shifts for regular and irregular workers on the
website and let workers manage their shifts with an website and let workers manage their shifts with an
easy web interface. easy web interface.
""", """,
'description': """
"description": """
""", """,
"author": "Coop IT Easy SCRLfs", "author": "Coop IT Easy SCRLfs",
"license": "AGPL-3", "license": "AGPL-3",

355
beesdoo_website_shift/controllers/main.py

@ -6,39 +6,41 @@
from ast import literal_eval from ast import literal_eval
from datetime import datetime, timedelta from datetime import datetime, timedelta
from itertools import groupby from itertools import groupby
from pytz import timezone, utc from pytz import timezone, utc
from odoo import http, fields
from odoo import fields, http
from odoo.http import request from odoo.http import request
from odoo.addons.beesdoo_shift.models.planning import float_to_time from odoo.addons.beesdoo_shift.models.planning import float_to_time
class WebsiteShiftController(http.Controller): class WebsiteShiftController(http.Controller):
def is_user_worker(self): def is_user_worker(self):
user = request.env['res.users'].browse(request.uid)
user = request.env["res.users"].browse(request.uid)
return user.partner_id.is_worker return user.partner_id.is_worker
def is_user_irregular(self): def is_user_irregular(self):
user = request.env['res.users'].browse(request.uid)
user = request.env["res.users"].browse(request.uid)
working_mode = user.partner_id.working_mode working_mode = user.partner_id.working_mode
return working_mode == 'irregular'
return working_mode == "irregular"
def is_user_regular(self): def is_user_regular(self):
user = request.env['res.users'].browse(request.uid)
user = request.env["res.users"].browse(request.uid)
working_mode = user.partner_id.working_mode working_mode = user.partner_id.working_mode
return working_mode == 'regular'
return working_mode == "regular"
def is_user_regular_without_shift(self): def is_user_regular_without_shift(self):
user = request.env['res.users'].browse(request.uid)
return (not user.partner_id.subscribed_shift_ids.id
and self.is_user_regular())
user = request.env["res.users"].browse(request.uid)
return (
not user.partner_id.subscribed_shift_ids.id
and self.is_user_regular()
)
def is_user_exempted(self): def is_user_exempted(self):
user = request.env['res.users'].browse(request.uid)
user = request.env["res.users"].browse(request.uid)
working_mode = user.partner_id.working_mode working_mode = user.partner_id.working_mode
return working_mode == 'exempt'
return working_mode == "exempt"
def user_can_subscribe(self, user=None): def user_can_subscribe(self, user=None):
"""Return True if a user can subscribe to a shift. A user can """Return True if a user can subscribe to a shift. A user can
@ -48,10 +50,12 @@ class WebsiteShiftController(http.Controller):
* the user is not resigning * the user is not resigning
""" """
if not user: if not user:
user = request.env['res.users'].browse(request.uid)
return (user.partner_id.working_mode == 'irregular'
and user.partner_id.state != 'unsubscribed'
and user.partner_id.state != 'resigning')
user = request.env["res.users"].browse(request.uid)
return (
user.partner_id.working_mode == "irregular"
and user.partner_id.state != "unsubscribed"
and user.partner_id.state != "resigning"
)
def add_days(self, datetime, days): def add_days(self, datetime, days):
""" """
@ -66,12 +70,12 @@ class WebsiteShiftController(http.Controller):
assert datetime.tzinfo is None assert datetime.tzinfo is None
# Get current user and user timezone # Get current user and user timezone
# Take user tz, if empty use context tz, if empty use UTC # Take user tz, if empty use context tz, if empty use UTC
cur_user = request.env['res.users'].browse(request.uid)
cur_user = request.env["res.users"].browse(request.uid)
user_tz = utc user_tz = utc
if cur_user.tz: if cur_user.tz:
user_tz = timezone(cur_user.tz) user_tz = timezone(cur_user.tz)
elif request.env.context['tz']:
user_tz = timezone(request.env.context['tz'])
elif request.env.context["tz"]:
user_tz = timezone(request.env.context["tz"])
# Convert to UTC # Convert to UTC
dt_utc = utc.localize(datetime, is_dst=False) dt_utc = utc.localize(datetime, is_dst=False)
# Convert to user TZ # Convert to user TZ
@ -86,43 +90,39 @@ class WebsiteShiftController(http.Controller):
newdt_utc = newdt_local.astimezone(utc) newdt_utc = newdt_local.astimezone(utc)
return newdt_utc.replace(tzinfo=None) return newdt_utc.replace(tzinfo=None)
@http.route('/my/shift', auth='user', website=True)
@http.route("/my/shift", auth="user", website=True)
def my_shift(self, **kw): def my_shift(self, **kw):
""" """
Personal page for managing your shifts Personal page for managing your shifts
""" """
if self.is_user_irregular(): if self.is_user_irregular():
return request.render( return request.render(
'beesdoo_website_shift.my_shift_irregular_worker',
self.my_shift_irregular_worker(nexturl='/my/shift')
"beesdoo_website_shift.my_shift_irregular_worker",
self.my_shift_irregular_worker(nexturl="/my/shift"),
) )
if self.is_user_regular_without_shift(): if self.is_user_regular_without_shift():
return request.render( return request.render(
'beesdoo_website_shift.my_shift_regular_worker_without_shift',
self.my_shift_regular_worker_without_shift()
"beesdoo_website_shift.my_shift_regular_worker_without_shift",
self.my_shift_regular_worker_without_shift(),
) )
if self.is_user_regular(): if self.is_user_regular():
return request.render( return request.render(
'beesdoo_website_shift.my_shift_regular_worker',
self.my_shift_regular_worker()
"beesdoo_website_shift.my_shift_regular_worker",
self.my_shift_regular_worker(),
) )
if self.is_user_exempted(): if self.is_user_exempted():
return request.render( return request.render(
'beesdoo_website_shift.my_shift_exempted_worker',
self.my_shift_exempted_worker()
"beesdoo_website_shift.my_shift_exempted_worker",
self.my_shift_exempted_worker(),
) )
if self.is_user_worker(): if self.is_user_worker():
return request.render( return request.render(
'beesdoo_website_shift.my_shift_new_worker',
{}
"beesdoo_website_shift.my_shift_new_worker", {}
) )
return request.render(
'beesdoo_website_shift.my_shift_non_worker',
{}
)
return request.render("beesdoo_website_shift.my_shift_non_worker", {})
@http.route('/shift/<int:shift_id>/subscribe', auth='user', website=True)
@http.route("/shift/<int:shift_id>/subscribe", auth="user", website=True)
def subscribe_to_shift(self, shift_id=-1, **kw): def subscribe_to_shift(self, shift_id=-1, **kw):
""" """
Subscribe the current connected user into the given shift Subscribe the current connected user into the given shift
@ -136,73 +136,74 @@ class WebsiteShiftController(http.Controller):
for attendance sheet generation defined in beesdoo_shift settings for attendance sheet generation defined in beesdoo_shift settings
""" """
# Get current user # Get current user
cur_user = request.env['res.users'].browse(request.uid)
cur_user = request.env["res.users"].browse(request.uid)
# Get the shift # Get the shift
shift = request.env['beesdoo.shift.shift'].sudo().browse(shift_id)
shift = request.env["beesdoo.shift.shift"].sudo().browse(shift_id)
# Get config # Get config
irregular_enable_sign_up = request.website.irregular_enable_sign_up irregular_enable_sign_up = request.website.irregular_enable_sign_up
# Set start time limit as defined in beesdoo_shift settings # Set start time limit as defined in beesdoo_shift settings
# TODO: Move this into the attendance_sheet module # TODO: Move this into the attendance_sheet module
# setting = request.website.attendance_sheet_generation_interval # setting = request.website.attendance_sheet_generation_interval
start_time_limit = datetime.now() # + timedelta(minutes=setting) start_time_limit = datetime.now() # + timedelta(minutes=setting)
request.session['success'] = False
if (irregular_enable_sign_up
and self.user_can_subscribe()
and shift
and shift.state == "open"
and shift.start_time > start_time_limit
and not shift.worker_id):
request.session["success"] = False
if (
irregular_enable_sign_up
and self.user_can_subscribe()
and shift
and shift.state == "open"
and shift.start_time > start_time_limit
and not shift.worker_id
):
shift.worker_id = cur_user.partner_id shift.worker_id = cur_user.partner_id
request.session['success'] = True
return request.redirect(kw['nexturl'])
request.session["success"] = True
return request.redirect(kw["nexturl"])
@http.route('/shift_irregular_worker', auth='public', website=True)
@http.route("/shift_irregular_worker", auth="public", website=True)
def public_shift_irregular_worker(self, **kw): def public_shift_irregular_worker(self, **kw):
""" """
Show a public access page that show all the available shifts for irregular worker. Show a public access page that show all the available shifts for irregular worker.
""" """
nexturl = '/shift_irregular_worker'
nexturl = "/shift_irregular_worker"
irregular_enable_sign_up = False irregular_enable_sign_up = False
# Create template context # Create template context
template_context = {} template_context = {}
template_context.update(self.available_shift_irregular_worker(
irregular_enable_sign_up, nexturl
))
template_context.update(
self.available_shift_irregular_worker(
irregular_enable_sign_up, nexturl
)
)
return request.render( return request.render(
'beesdoo_website_shift.public_shift_irregular_worker',
template_context
"beesdoo_website_shift.public_shift_irregular_worker",
template_context,
) )
@http.route('/shift_template_regular_worker', auth='public', website=True)
@http.route("/shift_template_regular_worker", auth="public", website=True)
def public_shift_template_regular_worker(self, **kw): def public_shift_template_regular_worker(self, **kw):
""" """
Show a public access page that show all the available shift templates for regular worker. Show a public access page that show all the available shift templates for regular worker.
""" """
# Get all the task template # Get all the task template
template = request.env['beesdoo.shift.template']
task_templates = template.sudo().search([], order="planning_id, day_nb_id, start_time")
template = request.env["beesdoo.shift.template"]
task_templates = template.sudo().search(
[], order="planning_id, day_nb_id, start_time"
)
# Get config # Get config
regular_highlight_rule = request.website.regular_highlight_rule regular_highlight_rule = request.website.regular_highlight_rule
task_tpls_data = [] task_tpls_data = []
for task_tpl in task_templates: for task_tpl in task_templates:
has_enough_workers = (
task_tpl.remaining_worker <= (
task_tpl.worker_nb * regular_highlight_rule / 100
)
has_enough_workers = task_tpl.remaining_worker <= (
task_tpl.worker_nb * regular_highlight_rule / 100
) )
task_tpls_data.append((task_tpl, has_enough_workers)) task_tpls_data.append((task_tpl, has_enough_workers))
return request.render( return request.render(
'beesdoo_website_shift.public_shift_template_regular_worker',
{
'task_tpls_data': task_tpls_data,
'float_to_time': float_to_time,
}
"beesdoo_website_shift.public_shift_template_regular_worker",
{"task_tpls_data": task_tpls_data, "float_to_time": float_to_time},
) )
def my_shift_irregular_worker(self, nexturl=""): def my_shift_irregular_worker(self, nexturl=""):
@ -218,16 +219,18 @@ class WebsiteShiftController(http.Controller):
template_context.update(self.my_shift_worker_status()) template_context.update(self.my_shift_worker_status())
template_context.update(self.my_shift_next_shifts()) template_context.update(self.my_shift_next_shifts())
template_context.update(self.my_shift_past_shifts()) template_context.update(self.my_shift_past_shifts())
template_context.update(self.available_shift_irregular_worker(
irregular_enable_sign_up and self.user_can_subscribe(), nexturl
))
template_context.update(
self.available_shift_irregular_worker(
irregular_enable_sign_up and self.user_can_subscribe(), nexturl
)
)
# Add feedback about the success or the fail of the subscription # Add feedback about the success or the fail of the subscription
template_context['back_from_subscription'] = False
if 'success' in request.session:
template_context['back_from_subscription'] = True
template_context['success'] = request.session.get('success')
del request.session['success']
template_context["back_from_subscription"] = False
if "success" in request.session:
template_context["back_from_subscription"] = True
template_context["success"] = request.session.get("success")
del request.session["success"]
# Add setting for subscription allowed time # Add setting for subscription allowed time
# TODO: move this to the attendance_sheet module # TODO: move this to the attendance_sheet module
@ -235,7 +238,7 @@ class WebsiteShiftController(http.Controller):
# request.website.attendance_sheet_generation_interval # request.website.attendance_sheet_generation_interval
# ) # )
subscription_time_limit = 0 subscription_time_limit = 0
template_context['subscription_time_limit'] = subscription_time_limit
template_context["subscription_time_limit"] = subscription_time_limit
return template_context return template_context
@ -253,17 +256,16 @@ class WebsiteShiftController(http.Controller):
template_context = {} template_context = {}
# Get all the task template # Get all the task template
template = request.env['beesdoo.shift.template']
task_templates = template.sudo().search([], order="planning_id, day_nb_id, start_time")
template = request.env["beesdoo.shift.template"]
task_templates = template.sudo().search(
[], order="planning_id, day_nb_id, start_time"
)
template_context.update(self.my_shift_worker_status()) template_context.update(self.my_shift_worker_status())
template_context.update(self.my_shift_next_shifts()) template_context.update(self.my_shift_next_shifts())
template_context.update(self.my_shift_past_shifts()) template_context.update(self.my_shift_past_shifts())
template_context.update( template_context.update(
{
'task_templates': task_templates,
'float_to_time': float_to_time,
}
{"task_templates": task_templates, "float_to_time": float_to_time}
) )
return template_context return template_context
@ -273,29 +275,42 @@ class WebsiteShiftController(http.Controller):
""" """
return self.my_shift_worker_status() return self.my_shift_worker_status()
def available_shift_irregular_worker(self, irregular_enable_sign_up=False,
nexturl=""):
def available_shift_irregular_worker(
self, irregular_enable_sign_up=False, nexturl=""
):
""" """
Return template variables for Return template variables for
'beesdoo_website_shift.available_shift_irregular_worker' template 'beesdoo_website_shift.available_shift_irregular_worker' template
""" """
# Get current user # Get current user
cur_user = request.env['res.users'].browse(request.uid)
cur_user = request.env["res.users"].browse(request.uid)
# Get all the shifts in the future with no worker # Get all the shifts in the future with no worker
now = datetime.now() now = datetime.now()
shifts = request.env['beesdoo.shift.shift'].sudo().search(
[('start_time', '>', now.strftime("%Y-%m-%d %H:%M:%S")),
('worker_id', '=', False),
('state', '=', 'open')],
order="start_time, task_template_id, task_type_id",
shifts = (
request.env["beesdoo.shift.shift"]
.sudo()
.search(
[
("start_time", ">", now.strftime("%Y-%m-%d %H:%M:%S")),
("worker_id", "=", False),
("state", "=", "open"),
],
order="start_time, task_template_id, task_type_id",
)
) )
# Get shifts where user is subscribed # Get shifts where user is subscribed
subscribed_shifts = request.env['beesdoo.shift.shift'].sudo().search(
[('start_time', '>', now.strftime("%Y-%m-%d %H:%M:%S")),
('worker_id', '=', cur_user.partner_id.id)],
order="start_time, task_template_id, task_type_id",
subscribed_shifts = (
request.env["beesdoo.shift.shift"]
.sudo()
.search(
[
("start_time", ">", now.strftime("%Y-%m-%d %H:%M:%S")),
("worker_id", "=", cur_user.partner_id.id),
],
order="start_time, task_template_id, task_type_id",
)
) )
# Get config # Get config
@ -308,7 +323,7 @@ class WebsiteShiftController(http.Controller):
# task_type # task_type
groupby_iter = groupby( groupby_iter = groupby(
shifts, shifts,
lambda s: (s.task_template_id, s.start_time, s.task_type_id)
lambda s: (s.task_template_id, s.start_time, s.task_type_id),
) )
shifts_count_subscribed = [] shifts_count_subscribed = []
@ -321,29 +336,39 @@ class WebsiteShiftController(http.Controller):
free_space = len(shift_list) free_space = len(shift_list)
# Is the current user subscribed to this task_template # Is the current user subscribed to this task_template
is_subscribed = any( is_subscribed = any(
(sub_shift.task_template_id == task_template and
sub_shift.start_time == start_time and
sub_shift.task_type_id == task_type)
for sub_shift in subscribed_shifts)
(
sub_shift.task_template_id == task_template
and sub_shift.start_time == start_time
and sub_shift.task_type_id == task_type
)
for sub_shift in subscribed_shifts
)
# Check the necessary number of worker based on the # Check the necessary number of worker based on the
# highlight_rule_pc # highlight_rule_pc
has_enough_workers = free_space <= (task_template.worker_nb
* highlight_rule_pc) / 100
has_enough_workers = (
free_space
<= (task_template.worker_nb * highlight_rule_pc) / 100
)
if free_space >= task_template.worker_nb * hide_rule: if free_space >= task_template.worker_nb * hide_rule:
shifts_count_subscribed.append([
shift_list[0],
free_space,
is_subscribed,
has_enough_workers,
])
shifts_count_subscribed.append(
[
shift_list[0],
free_space,
is_subscribed,
has_enough_workers,
]
)
# Stop showing shifts if the limit is reached # Stop showing shifts if the limit is reached
if irregular_shift_limit > 0 and nb_displayed_shift >= irregular_shift_limit:
if (
irregular_shift_limit > 0
and nb_displayed_shift >= irregular_shift_limit
):
break break
return { return {
'shift_templates': shifts_count_subscribed,
'nexturl': nexturl,
'irregular_enable_sign_up': irregular_enable_sign_up,
"shift_templates": shifts_count_subscribed,
"nexturl": nexturl,
"irregular_enable_sign_up": irregular_enable_sign_up,
} }
def my_shift_next_shifts(self): def my_shift_next_shifts(self):
@ -351,13 +376,19 @@ class WebsiteShiftController(http.Controller):
Return template variables for 'beesdoo_website_shift.my_shift_next_shifts' template Return template variables for 'beesdoo_website_shift.my_shift_next_shifts' template
""" """
# Get current user # Get current user
cur_user = request.env['res.users'].browse(request.uid)
cur_user = request.env["res.users"].browse(request.uid)
# Get shifts where user is subscribed # Get shifts where user is subscribed
now = datetime.now() now = datetime.now()
subscribed_shifts_rec = request.env['beesdoo.shift.shift'].sudo().search(
[('start_time', '>', now.strftime("%Y-%m-%d %H:%M:%S")),
('worker_id', '=', cur_user.partner_id.id)],
order="start_time, task_template_id, task_type_id",
subscribed_shifts_rec = (
request.env["beesdoo.shift.shift"]
.sudo()
.search(
[
("start_time", ">", now.strftime("%Y-%m-%d %H:%M:%S")),
("worker_id", "=", cur_user.partner_id.id),
],
order="start_time, task_template_id, task_type_id",
)
) )
# Create a list of record in order to add new record to it later # Create a list of record in order to add new record to it later
subscribed_shifts = [] subscribed_shifts = []
@ -372,22 +403,34 @@ class WebsiteShiftController(http.Controller):
if nb_subscribed_shifts > 0: if nb_subscribed_shifts > 0:
main_shift = subscribed_shifts[-1] main_shift = subscribed_shifts[-1]
else: else:
task_template = request.env['beesdoo.shift.template'].sudo().search(
[('worker_ids', 'in', cur_user.partner_id.id)],
limit=1,
task_template = (
request.env["beesdoo.shift.template"]
.sudo()
.search(
[("worker_ids", "in", cur_user.partner_id.id)], limit=1
)
) )
main_shift = request.env['beesdoo.shift.shift'].sudo().search(
[('task_template_id', '=', task_template[0].id),
('start_time', '!=', False),
('end_time', '!=', False)],
order="start_time desc",
limit=1,
main_shift = (
request.env["beesdoo.shift.shift"]
.sudo()
.search(
[
("task_template_id", "=", task_template[0].id),
("start_time", "!=", False),
("end_time", "!=", False),
],
order="start_time desc",
limit=1,
)
) )
# Get config # Get config
regular_next_shift_limit = request.website.regular_next_shift_limit regular_next_shift_limit = request.website.regular_next_shift_limit
shift_period = int(request.env['ir.config_parameter'].get_param(
'beesdoo_website_shift.shift_period'))
shift_period = int(
request.env["ir.config_parameter"].get_param(
"beesdoo_website_shift.shift_period"
)
)
for i in range(nb_subscribed_shifts, regular_next_shift_limit): for i in range(nb_subscribed_shifts, regular_next_shift_limit):
# Create the fictive shift # Create the fictive shift
@ -405,19 +448,17 @@ class WebsiteShiftController(http.Controller):
shift.revert_info = main_shift.revert_info shift.revert_info = main_shift.revert_info
# Set new date # Set new date
shift.start_time = self.add_days( shift.start_time = self.add_days(
main_shift.start_time,
days=i * shift_period
main_shift.start_time, days=i * shift_period
) )
shift.end_time = self.add_days( shift.end_time = self.add_days(
main_shift.end_time,
days=i * shift_period
main_shift.end_time, days=i * shift_period
) )
# Add the fictive shift to the list of shift # Add the fictive shift to the list of shift
subscribed_shifts.append(shift) subscribed_shifts.append(shift)
return { return {
'is_regular': self.is_user_regular(),
'subscribed_shifts': subscribed_shifts,
"is_regular": self.is_user_regular(),
"subscribed_shifts": subscribed_shifts,
} }
def my_shift_past_shifts(self): def my_shift_past_shifts(self):
@ -425,7 +466,7 @@ class WebsiteShiftController(http.Controller):
Return template variables for 'beesdoo_website_shift.my_shift_past_shifts' template Return template variables for 'beesdoo_website_shift.my_shift_past_shifts' template
""" """
# Get current user # Get current user
cur_user = request.env['res.users'].browse(request.uid)
cur_user = request.env["res.users"].browse(request.uid)
# Get config # Get config
past_shift_limit = 0 past_shift_limit = 0
if self.is_user_irregular(): if self.is_user_irregular():
@ -435,28 +476,44 @@ class WebsiteShiftController(http.Controller):
# Get shifts where user was subscribed # Get shifts where user was subscribed
now = datetime.now() now = datetime.now()
if past_shift_limit > 0: if past_shift_limit > 0:
past_shifts = request.env['beesdoo.shift.shift'].sudo().search(
[('start_time', '<=', now.strftime("%Y-%m-%d %H:%M:%S")),
('worker_id', '=', cur_user.partner_id.id)],
order="start_time desc, task_template_id, task_type_id",
limit=past_shift_limit,
past_shifts = (
request.env["beesdoo.shift.shift"]
.sudo()
.search(
[
(
"start_time",
"<=",
now.strftime("%Y-%m-%d %H:%M:%S"),
),
("worker_id", "=", cur_user.partner_id.id),
],
order="start_time desc, task_template_id, task_type_id",
limit=past_shift_limit,
)
) )
else: else:
past_shifts = request.env['beesdoo.shift.shift'].sudo().search(
[('start_time', '<=', now.strftime("%Y-%m-%d %H:%M:%S")),
('worker_id', '=', cur_user.partner_id.id)],
order="start_time desc, task_template_id, task_type_id",
past_shifts = (
request.env["beesdoo.shift.shift"]
.sudo()
.search(
[
(
"start_time",
"<=",
now.strftime("%Y-%m-%d %H:%M:%S"),
),
("worker_id", "=", cur_user.partner_id.id),
],
order="start_time desc, task_template_id, task_type_id",
)
) )
return {
'past_shifts': past_shifts,
}
return {"past_shifts": past_shifts}
def my_shift_worker_status(self): def my_shift_worker_status(self):
""" """
Return template variables for 'beesdoo_website_shift.my_shift_worker_status_*' template Return template variables for 'beesdoo_website_shift.my_shift_worker_status_*' template
""" """
cur_user = request.env['res.users'].browse(request.uid)
return {
'status': cur_user.partner_id.cooperative_status_ids,
}
cur_user = request.env["res.users"].browse(request.uid)
return {"status": cur_user.partner_id.cooperative_status_ids}

29
beesdoo_website_shift/models/res_config.py

@ -2,44 +2,37 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from ast import literal_eval from ast import literal_eval
from odoo import fields, models, api
from odoo import api, fields, models
class WebsiteShiftConfigSettings(models.TransientModel): class WebsiteShiftConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
_inherit = "res.config.settings"
# Irregular worker settings # Irregular worker settings
irregular_shift_limit = fields.Integer( irregular_shift_limit = fields.Integer(
related='website_id.irregular_shift_limit',
readonly=False,
related="website_id.irregular_shift_limit", readonly=False
) )
highlight_rule_pc = fields.Integer( highlight_rule_pc = fields.Integer(
related='website_id.highlight_rule_pc',
readonly=False,
related="website_id.highlight_rule_pc", readonly=False
) )
hide_rule = fields.Integer( hide_rule = fields.Integer(
related='website_id.highlight_rule_pc',
readonly=False,
related="website_id.highlight_rule_pc", readonly=False
) )
irregular_enable_sign_up = fields.Boolean( irregular_enable_sign_up = fields.Boolean(
related='website_id.irregular_enable_sign_up',
readonly=False,
related="website_id.irregular_enable_sign_up", readonly=False
) )
irregular_past_shift_limit = fields.Integer( irregular_past_shift_limit = fields.Integer(
related='website_id.irregular_past_shift_limit',
readonly=False,
related="website_id.irregular_past_shift_limit", readonly=False
) )
# Regular worker settings # Regular worker settings
regular_past_shift_limit = fields.Integer( regular_past_shift_limit = fields.Integer(
related='website_id.regular_past_shift_limit',
readonly=False,
related="website_id.regular_past_shift_limit", readonly=False
) )
regular_next_shift_limit = fields.Integer( regular_next_shift_limit = fields.Integer(
related='website_id.regular_next_shift_limit',
readonly=False,
related="website_id.regular_next_shift_limit", readonly=False
) )
regular_highlight_rule = fields.Integer( regular_highlight_rule = fields.Integer(
related='website_id.regular_highlight_rule',
readonly=False,
related="website_id.regular_highlight_rule", readonly=False
) )

21
beesdoo_website_shift/models/website.py

@ -5,43 +5,40 @@ from odoo import fields, models
class Website(models.Model): class Website(models.Model):
_inherit = 'website'
_inherit = "website"
# Irregular worker settings # Irregular worker settings
irregular_shift_limit = fields.Integer( irregular_shift_limit = fields.Integer(
default=0,
help="Maximum shift that will be shown"
default=0, help="Maximum shift that will be shown"
) )
highlight_rule_pc = fields.Integer( highlight_rule_pc = fields.Integer(
default=30, default=30,
help="Treshold (in %) of available space in a shift that trigger the " help="Treshold (in %) of available space in a shift that trigger the "
"highlight of the shift"
"highlight of the shift",
) )
hide_rule = fields.Integer( hide_rule = fields.Integer(
default=20, default=20,
help="Treshold ((available space)/(max space)) in percentage of " help="Treshold ((available space)/(max space)) in percentage of "
"available space under wich the shift is hidden"
"available space under wich the shift is hidden",
) )
irregular_enable_sign_up = fields.Boolean( irregular_enable_sign_up = fields.Boolean(
default=True,
help="Enable shift sign up for irregular worker"
default=True, help="Enable shift sign up for irregular worker"
) )
irregular_past_shift_limit = fields.Integer( irregular_past_shift_limit = fields.Integer(
default=10, default=10,
help="Maximum past shift that will be shown for irregular worker"
help="Maximum past shift that will be shown for irregular worker",
) )
# Regular worker settings # Regular worker settings
regular_past_shift_limit = fields.Integer( regular_past_shift_limit = fields.Integer(
default=10, default=10,
help="Maximum past shift that will be shown for regular worker"
help="Maximum past shift that will be shown for regular worker",
) )
regular_next_shift_limit = fields.Integer( regular_next_shift_limit = fields.Integer(
default=13,
help="Maximun number of next shift that will be shown"
default=13, help="Maximun number of next shift that will be shown"
) )
regular_highlight_rule = fields.Integer( regular_highlight_rule = fields.Integer(
default=20, default=20,
help="Treshold (in %) of available space in a shift that trigger the " help="Treshold (in %) of available space in a shift that trigger the "
"the highlight of a shift template."
"the highlight of a shift template.",
) )

2
beesdoo_website_shift/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Beescoop - Cellule IT
* Coop IT Easy SCRLfs

29
beesdoo_website_theme/__manifest__.py

@ -1,23 +1,16 @@
{ {
'name': 'BEES coop Website Theme',
'summary': """
"name": "BEES coop Website Theme",
"summary": """
Apply BEES coop design rules. Apply BEES coop design rules.
""", """,
'description': """
"description": """
""", """,
'author': 'Rémy Taymans',
'website': "https://github.com/beescoop/Obeesdoo",
'license': "AGPL-3",
'category': 'Themes',
'version': '12.0.0.1',
'application': True,
'depends': ['website'],
'data': [
'views/assets.xml',
]
"author": "Rémy Taymans",
"website": "https://github.com/beescoop/Obeesdoo",
"license": "AGPL-3",
"category": "Themes",
"version": "12.0.0.1",
"application": True,
"depends": ["website"],
"data": ["views/assets.xml"],
} }

2
beesdoo_website_theme/readme/CONTRIBUTORS.rst

@ -0,0 +1,2 @@
* Beescoop - Cellule IT
* Coop IT Easy SCRLfs

40
beesdoo_worker_status/__manifest__.py

@ -1,28 +1,16 @@
# Copyright 2020 Coop IT Easy SCRL fs
# Elouan Le Bars <elouan@coopiteasy.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{ {
'name': "Beescoop Worker Status manager",
'summary': """
Worker status management specific to beescoop""",
'description': """
""",
'author': "Thibault Francois, Elouan Le Bars, Coop It Easy",
'website': "https://github.com/beescoop/Obeesdoo",
'category': 'Cooperative management',
'version': '12.0.1.0.0',
'depends': [
'beesdoo_base',
'beesdoo_shift',
],
'data': [
],
'demo': [
"demo/cooperators.xml",
"demo/tasks.xml",
]
"name": "Beescoop Worker Status manager",
"summary": """
Worker status management specific to beescoop.""",
"author": "Thibault Francois, Elouan Le Bars, Coop IT Easy SCRLfs",
"website": "https://github.com/beescoop/Obeesdoo",
"category": "Cooperative management",
"version": "12.0.1.0.0",
"depends": ["beesdoo_base", "beesdoo_shift",],
"data": [],
"demo": ["demo/cooperators.xml", "demo/tasks.xml",],
"license": "AGPL-3",
} }

256
beesdoo_worker_status/models/cooperative_status.py

@ -1,13 +1,14 @@
from odoo import models, fields, api, _
from odoo.addons.beesdoo_shift.models.cooperative_status import add_days_delta
from odoo.exceptions import ValidationError, UserError
from datetime import timedelta, datetime
import logging import logging
from datetime import datetime, timedelta
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.addons.beesdoo_shift.models.cooperative_status import add_days_delta
class CooperativeStatus(models.Model): class CooperativeStatus(models.Model):
_inherit = 'cooperative.status'
_inherit = "cooperative.status"
_period = 28 _period = 28
###################################################### ######################################################
@ -16,17 +17,26 @@ class CooperativeStatus(models.Model):
# # # #
###################################################### ######################################################
future_alert_date = fields.Date(compute='_compute_future_alert_date')
next_countdown_date = fields.Date(compute='_compute_next_countdown_date')
future_alert_date = fields.Date(compute="_compute_future_alert_date")
next_countdown_date = fields.Date(compute="_compute_next_countdown_date")
@api.depends('today', 'irregular_start_date', 'sr', 'holiday_start_time',
'holiday_end_time', 'temporary_exempt_start_date',
'temporary_exempt_end_date')
@api.depends(
"today",
"irregular_start_date",
"sr",
"holiday_start_time",
"holiday_end_time",
"temporary_exempt_start_date",
"temporary_exempt_end_date",
)
def _compute_future_alert_date(self): def _compute_future_alert_date(self):
"""Compute date before which the worker is up to date""" """Compute date before which the worker is up to date"""
for rec in self: for rec in self:
# Only for irregular worker # Only for irregular worker
if rec.working_mode != 'irregular' and not rec.irregular_start_date:
if (
rec.working_mode != "irregular"
and not rec.irregular_start_date
):
rec.future_alert_date = False rec.future_alert_date = False
# Alert start time already set # Alert start time already set
elif rec.alert_start_time: elif rec.alert_start_time:
@ -35,8 +45,9 @@ class CooperativeStatus(models.Model):
elif bool(rec.holiday_start_time) != bool(rec.holiday_end_time): elif bool(rec.holiday_start_time) != bool(rec.holiday_end_time):
rec.future_alert_date = False rec.future_alert_date = False
# Exemption have not a start and end time # Exemption have not a start and end time
elif (bool(rec.temporary_exempt_start_date)
!= bool(rec.temporary_exempt_end_date)):
elif bool(rec.temporary_exempt_start_date) != bool(
rec.temporary_exempt_end_date
):
rec.future_alert_date = False rec.future_alert_date = False
else: else:
date = rec.today date = rec.today
@ -48,7 +59,8 @@ class CooperativeStatus(models.Model):
) )
# Check holidays # Check holidays
if ( if (
rec.holiday_start_time and rec.holiday_end_time
rec.holiday_start_time
and rec.holiday_end_time
and date >= rec.holiday_start_time and date >= rec.holiday_start_time
and date <= rec.holiday_end_time and date <= rec.holiday_end_time
): ):
@ -70,9 +82,14 @@ class CooperativeStatus(models.Model):
rec.irregular_start_date, date rec.irregular_start_date, date
) )
@api.depends('today', 'irregular_start_date', 'holiday_start_time',
'holiday_end_time', 'temporary_exempt_start_date',
'temporary_exempt_end_date')
@api.depends(
"today",
"irregular_start_date",
"holiday_start_time",
"holiday_end_time",
"temporary_exempt_start_date",
"temporary_exempt_end_date",
)
def _compute_next_countdown_date(self): def _compute_next_countdown_date(self):
""" """
Compute the following countdown date. This date is the date when Compute the following countdown date. This date is the date when
@ -82,14 +99,18 @@ class CooperativeStatus(models.Model):
""" """
for rec in self: for rec in self:
# Only for irregular worker # Only for irregular worker
if rec.working_mode != 'irregular' and not rec.irregular_start_date:
if (
rec.working_mode != "irregular"
and not rec.irregular_start_date
):
rec.next_countdown_date = False rec.next_countdown_date = False
# Holidays are not set properly # Holidays are not set properly
elif bool(rec.holiday_start_time) != bool(rec.holiday_end_time): elif bool(rec.holiday_start_time) != bool(rec.holiday_end_time):
rec.next_countdown_date = False rec.next_countdown_date = False
# Exemption have not a start and end time # Exemption have not a start and end time
elif (bool(rec.temporary_exempt_start_date)
!= bool(rec.temporary_exempt_end_date)):
elif bool(rec.temporary_exempt_start_date) != bool(
rec.temporary_exempt_end_date
):
rec.next_countdown_date = False rec.next_countdown_date = False
else: else:
date = rec.today date = rec.today
@ -100,7 +121,8 @@ class CooperativeStatus(models.Model):
) )
# Check holidays # Check holidays
if ( if (
rec.holiday_start_time and rec.holiday_end_time
rec.holiday_start_time
and rec.holiday_end_time
and date >= rec.holiday_start_time and date >= rec.holiday_start_time
and date <= rec.holiday_end_time and date <= rec.holiday_end_time
): ):
@ -124,28 +146,60 @@ class CooperativeStatus(models.Model):
##################################### #####################################
def _get_regular_status(self): def _get_regular_status(self):
self.ensure_one() self.ensure_one()
counter_unsubscribe = int(self.env['ir.config_parameter'].sudo().get_param('regular_counter_to_unsubscribe', -4))
alert_delay = int(self.env['ir.config_parameter'].sudo().get_param('alert_delay', 28))
grace_delay = int(self.env['ir.config_parameter'].sudo().get_param('default_grace_delay', 10))
counter_unsubscribe = int(
self.env["ir.config_parameter"]
.sudo()
.get_param("regular_counter_to_unsubscribe", -4)
)
alert_delay = int(
self.env["ir.config_parameter"].sudo().get_param("alert_delay", 28)
)
grace_delay = int(
self.env["ir.config_parameter"]
.sudo()
.get_param("default_grace_delay", 10)
)
ok = self.sr >= 0 and self.sc >= 0 ok = self.sr >= 0 and self.sc >= 0
grace_delay = grace_delay + self.time_extension grace_delay = grace_delay + self.time_extension
if (self.sr + self.sc) <= counter_unsubscribe or self.unsubscribed: if (self.sr + self.sc) <= counter_unsubscribe or self.unsubscribed:
return 'unsubscribed'
return "unsubscribed"
# Check if exempted. Exempt end date is not required. # Check if exempted. Exempt end date is not required.
if self.temporary_exempt_start_date and self.today >= self.temporary_exempt_start_date:
if not self.temporary_exempt_end_date or self.today <= self.temporary_exempt_end_date:
return 'exempted'
if (
self.temporary_exempt_start_date
and self.today >= self.temporary_exempt_start_date
):
if (
not self.temporary_exempt_end_date
or self.today <= self.temporary_exempt_end_date
):
return "exempted"
# Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined # Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined
if not ok and self.alert_start_time and self.extension_start_time and self.today <= add_days_delta(self.extension_start_time, grace_delay):
return 'extension'
if not ok and self.alert_start_time and self.extension_start_time and self.today > add_days_delta(self.extension_start_time, grace_delay):
return 'suspended'
if not ok and self.alert_start_time and self.today > add_days_delta(self.alert_start_time, alert_delay):
return 'suspended'
if (
not ok
and self.alert_start_time
and self.extension_start_time
and self.today
<= add_days_delta(self.extension_start_time, grace_delay)
):
return "extension"
if (
not ok
and self.alert_start_time
and self.extension_start_time
and self.today
> add_days_delta(self.extension_start_time, grace_delay)
):
return "suspended"
if (
not ok
and self.alert_start_time
and self.today > add_days_delta(self.alert_start_time, alert_delay)
):
return "suspended"
if (self.sr < 0) or (not ok and self.alert_start_time): if (self.sr < 0) or (not ok and self.alert_start_time):
return 'alert'
return "alert"
if ( if (
self.holiday_start_time self.holiday_start_time
@ -154,32 +208,64 @@ class CooperativeStatus(models.Model):
and self.today <= self.holiday_end_time and self.today <= self.holiday_end_time
): ):
return 'holiday'
return "holiday"
elif ok or (not self.alert_start_time and self.sr >= 0): elif ok or (not self.alert_start_time and self.sr >= 0):
return 'ok'
return "ok"
def _get_irregular_status(self): def _get_irregular_status(self):
self.ensure_one() self.ensure_one()
counter_unsubscribe = int(self.env['ir.config_parameter'].sudo().get_param('irregular_counter_to_unsubscribe', -3))
alert_delay = int(self.env['ir.config_parameter'].sudo().get_param('alert_delay', 28))
grace_delay = int(self.env['ir.config_parameter'].sudo().get_param('default_grace_delay', 10))
counter_unsubscribe = int(
self.env["ir.config_parameter"]
.sudo()
.get_param("irregular_counter_to_unsubscribe", -3)
)
alert_delay = int(
self.env["ir.config_parameter"].sudo().get_param("alert_delay", 28)
)
grace_delay = int(
self.env["ir.config_parameter"]
.sudo()
.get_param("default_grace_delay", 10)
)
ok = self.sr >= 0 ok = self.sr >= 0
grace_delay = grace_delay + self.time_extension grace_delay = grace_delay + self.time_extension
if self.sr <= counter_unsubscribe or self.unsubscribed: if self.sr <= counter_unsubscribe or self.unsubscribed:
return 'unsubscribed'
return "unsubscribed"
# Check if exempted. Exempt end date is not required. # Check if exempted. Exempt end date is not required.
elif self.temporary_exempt_start_date and self.today >= self.temporary_exempt_start_date:
if not self.temporary_exempt_end_date or self.today <= self.temporary_exempt_end_date:
return 'exempted'
elif (
self.temporary_exempt_start_date
and self.today >= self.temporary_exempt_start_date
):
if (
not self.temporary_exempt_end_date
or self.today <= self.temporary_exempt_end_date
):
return "exempted"
# Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined # Transition to alert sr < 0 or stay in alert sr < 0 or sc < 0 and thus alert time is defined
elif not ok and self.alert_start_time and self.extension_start_time and self.today <= add_days_delta(self.extension_start_time, grace_delay):
return 'extension'
elif not ok and self.alert_start_time and self.extension_start_time and self.today > add_days_delta(self.extension_start_time, grace_delay):
return 'suspended'
elif not ok and self.alert_start_time and self.today > add_days_delta(self.alert_start_time, alert_delay):
return 'suspended'
elif (
not ok
and self.alert_start_time
and self.extension_start_time
and self.today
<= add_days_delta(self.extension_start_time, grace_delay)
):
return "extension"
elif (
not ok
and self.alert_start_time
and self.extension_start_time
and self.today
> add_days_delta(self.extension_start_time, grace_delay)
):
return "suspended"
elif (
not ok
and self.alert_start_time
and self.today > add_days_delta(self.alert_start_time, alert_delay)
):
return "suspended"
elif (self.sr < 0) or (not ok and self.alert_start_time): elif (self.sr < 0) or (not ok and self.alert_start_time):
return 'alert'
return "alert"
elif ( elif (
self.holiday_start_time self.holiday_start_time
@ -187,29 +273,36 @@ class CooperativeStatus(models.Model):
and self.today >= self.holiday_start_time and self.today >= self.holiday_start_time
and self.today <= self.holiday_end_time and self.today <= self.holiday_end_time
): ):
return 'holiday'
return "holiday"
elif ok or (not self.alert_start_time and self.sr >= 0): elif ok or (not self.alert_start_time and self.sr >= 0):
return 'ok'
return "ok"
def _state_change(self, new_state): def _state_change(self, new_state):
self.ensure_one() self.ensure_one()
if new_state == 'alert':
self.write({'alert_start_time': self.today, 'extension_start_time': False, 'time_extension': 0})
if new_state == 'ok':
data = {'extension_start_time': False, 'time_extension': 0}
data['alert_start_time'] = False
if new_state == "alert":
self.write(
{
"alert_start_time": self.today,
"extension_start_time": False,
"time_extension": 0,
}
)
if new_state == "ok":
data = {"extension_start_time": False, "time_extension": 0}
data["alert_start_time"] = False
self.write(data) self.write(data)
if new_state == 'unsubscribed' or new_state == 'resigning':
if new_state == "unsubscribed" or new_state == "resigning":
# Remove worker from task_templates # Remove worker from task_templates
self.cooperator_id.sudo().write( self.cooperator_id.sudo().write(
{'subscribed_shift_ids': [(5, 0, 0)]})
{"subscribed_shift_ids": [(5, 0, 0)]}
)
# Remove worker from supercoop in task_templates # Remove worker from supercoop in task_templates
task_tpls = self.env['beesdoo.shift.template'].search(
[('super_coop_id', 'in', self.cooperator_id.user_ids.ids)]
task_tpls = self.env["beesdoo.shift.template"].search(
[("super_coop_id", "in", self.cooperator_id.user_ids.ids)]
) )
task_tpls.write({'super_coop_id': False})
task_tpls.write({"super_coop_id": False})
# Remove worker for future tasks (remove also supercoop) # Remove worker for future tasks (remove also supercoop)
self.env['beesdoo.shift.shift'].sudo().unsubscribe_from_today(
self.env["beesdoo.shift.shift"].sudo().unsubscribe_from_today(
[self.cooperator_id.id], now=fields.Datetime.now() [self.cooperator_id.id], now=fields.Datetime.now()
) )
@ -218,10 +311,12 @@ class CooperativeStatus(models.Model):
Call when a shift state is changed Call when a shift state is changed
use data generated by _get_counter_date_state_change use data generated by _get_counter_date_state_change
""" """
self.sc += data.get('sc', 0)
self.sr += data.get('sr', 0)
self.irregular_absence_counter += data.get('irregular_absence_counter', 0)
self.irregular_absence_date = data.get('irregular_absence_date', False)
self.sc += data.get("sc", 0)
self.sr += data.get("sr", 0)
self.irregular_absence_counter += data.get(
"irregular_absence_counter", 0
)
self.irregular_absence_date = data.get("irregular_absence_date", False)
############################################### ###############################################
###### Irregular Cron implementation ########## ###### Irregular Cron implementation ##########
@ -229,15 +324,20 @@ class CooperativeStatus(models.Model):
def _get_irregular_worker_domain(self, **kwargs): def _get_irregular_worker_domain(self, **kwargs):
today = kwargs.get("today") or self.today today = kwargs.get("today") or self.today
return ['&',
'&',
'&',
('status', 'not in', ['unsubscribed', 'exempted']),
('working_mode', '=', 'irregular'),
('irregular_start_date', '!=', False),
'|',
'|', ('holiday_start_time', '=', False), ('holiday_end_time', '=', False),
'|', ('holiday_start_time', '>', today), ('holiday_end_time', '<', today),
return [
"&",
"&",
"&",
("status", "not in", ["unsubscribed", "exempted"]),
("working_mode", "=", "irregular"),
("irregular_start_date", "!=", False),
"|",
"|",
("holiday_start_time", "=", False),
("holiday_end_time", "=", False),
"|",
("holiday_start_time", ">", today),
("holiday_end_time", "<", today),
] ]
def _change_irregular_counter(self): def _change_irregular_counter(self):

44
beesdoo_worker_status/models/task.py

@ -6,7 +6,7 @@ from odoo.exceptions import UserError, ValidationError
class Task(models.Model): class Task(models.Model):
_inherit = 'beesdoo.shift.shift'
_inherit = "beesdoo.shift.shift"
################################# #################################
# State Definition # # State Definition #
@ -14,12 +14,12 @@ class Task(models.Model):
def _get_selection_status(self): def _get_selection_status(self):
return [ return [
("open","Confirmed"),
("done","Attended"),
("absent_2","Absent - 2 compensations"),
("absent_1","Absent - 1 compensation"),
("absent_0","Absent - 0 compensation"),
("cancel","Cancelled")
("open", "Confirmed"),
("done", "Attended"),
("absent_2", "Absent - 2 compensations"),
("absent_1", "Absent - 1 compensation"),
("absent_0", "Absent - 0 compensation"),
("cancel", "Cancelled"),
] ]
def _get_color_mapping(self, state): def _get_color_mapping(self, state):
@ -43,7 +43,7 @@ class Task(models.Model):
############################################## ##############################################
def _get_counter_date_state_change(self, new_state): def _get_counter_date_state_change(self, new_state):
data = {} data = {}
if self.worker_id.working_mode == 'regular':
if self.worker_id.working_mode == "regular":
if not self.replaced_id: # No replacement case if not self.replaced_id: # No replacement case
status = self.worker_id.cooperative_status_ids[0] status = self.worker_id.cooperative_status_ids[0]
@ -53,29 +53,31 @@ class Task(models.Model):
if new_state == "done" and not self.is_regular: if new_state == "done" and not self.is_regular:
# Regular counter is always updated first # Regular counter is always updated first
if status.sr < 0: if status.sr < 0:
data['sr'] = 1
data["sr"] = 1
elif status.sc < 0: elif status.sc < 0:
data['sc'] = 1
data["sc"] = 1
# Bonus shift case # Bonus shift case
else: else:
data['sr'] = 1
data["sr"] = 1
if new_state == "absent_2": if new_state == "absent_2":
data['sr'] = -1
data['sc'] = -1
data["sr"] = -1
data["sc"] = -1
if new_state == "absent_1": if new_state == "absent_1":
data['sr'] = -1
data["sr"] = -1
elif self.worker_id.working_mode == 'irregular':
elif self.worker_id.working_mode == "irregular":
status = self.worker_id.cooperative_status_ids[0] status = self.worker_id.cooperative_status_ids[0]
if new_state == "done" or new_state == "absent_0": if new_state == "done" or new_state == "absent_0":
data['sr'] = 1
data['irregular_absence_date'] = False
data['irregular_absence_counter'] = 1 if status.irregular_absence_counter < 0 else 0
data["sr"] = 1
data["irregular_absence_date"] = False
data["irregular_absence_counter"] = (
1 if status.irregular_absence_counter < 0 else 0
)
if new_state == "absent_2" or new_state == "absent_1": if new_state == "absent_2" or new_state == "absent_1":
if new_state == "absent_2": if new_state == "absent_2":
data['sr'] = -1
data['irregular_absence_date'] = self.start_time.date()
data['irregular_absence_counter'] = -1
data["sr"] = -1
data["irregular_absence_date"] = self.start_time.date()
data["irregular_absence_counter"] = -1
return data, status return data, status

2
initial-data-load/01_readme.md

@ -1,4 +1,4 @@
# Put a database dump here to load it in postgresql
# Put a database dump here to load it in postgresql
Us the format `01_*.sql.gz` or `01_*.sql`. Us the format `01_*.sql.gz` or `01_*.sql`.
Files are loaded alphabetically, it needs to be loaded after the creation of the odoo user and before we disable cron. Files are loaded alphabetically, it needs to be loaded after the creation of the odoo user and before we disable cron.

14
install-odoo-docker.md

@ -26,20 +26,20 @@ docker-compose up db
``` ```
This could take a while (~20 minutes) depending on the data dump you are using. This could take a while (~20 minutes) depending on the data dump you are using.
In order to reset your database, remove the container.
In order to reset your database, remove the container.
(It will have to rebuild the database the next time you start it). (It will have to rebuild the database the next time you start it).
```bash ```bash
docker-compose rm db docker-compose rm db
``` ```
## 4) Run the project ## 4) Run the project
If you are not using a pre-created db run
If you are not using a pre-created db run
```bash ```bash
docker-compose run odoo python3 odoo-bin -d beescoop -i base -c odoo.conf docker-compose run odoo python3 odoo-bin -d beescoop -i base -c odoo.conf
``` ```
All the modules need to be updated in order to be recognised. All the modules need to be updated in order to be recognised.
To do that run
To do that run
```bash ```bash
docker-compose run -p 8096:8096 odoo python odoo.py -c odoo.conf -d beescoop -u all docker-compose run -p 8096:8096 odoo python odoo.py -c odoo.conf -d beescoop -u all
``` ```
@ -51,9 +51,9 @@ docker-compose up
I like to start the database in the background to only have the logs of the application. I like to start the database in the background to only have the logs of the application.
```bash ```bash
docker-compose up -d db
docker-compose up odoo
```
docker-compose up -d db
docker-compose up odoo
```
## 5) Login ## 5) Login
To login you may either use your account or the admin account whose password should have been reset to admin. To login you may either use your account or the admin account whose password should have been reset to admin.

512
install-odoo-linux-server.md

@ -1,259 +1,267 @@
# Install odoo on a linux server # Install odoo on a linux server
> by Thibault François
> by Thibault François
## Installation basique ## Installation basique
##### 1) ajouter un utilisateur odoo
# adduser odoo
##### 2) installation de postgresql (DBMS)
# apt-get install postgresql
##### 3) install git
# apt-get install git
##### 4) installer pip : python package manager
# apt-get install python-pip
##### 5) installation des paquets devel pour compilation des bibliothèques python
# apt-get install python-dev postgresql-server-dev-all libjpeg-dev zlib1g-dev libpng12-dev libxml2-dev libxslt1-dev libldap2-dev libsasl2-dev
##### 6) installation de node-less
# apt-get install node-less
##### 7) clone odoo
# su odoo
$ cd /home/odoo
$ git clone https://github.com/odoo/odoo.git
##### 8) installer bibliothèque python
$ exit
# cd /home/odoo/odoo
# pip install -r requirements.txt
##### 9) créer odoo user pour postgresql avec les droits de création de base de donnée
# su postgres
$ createuser -d odoo
$ exit
##### 10) Installer wkhtml to pdf 0.12.1 !! (pas une autre) (sur une machine 64 bit avec un ubuntu 64bit 14.04)
# apt-get install fontconfig libfontconfig1 libxrender1 fontconfig-config
# wget http://download.gna.org/wkhtmltopdf/0.12/0.12.1/wkhtmltox-0.12.1_linux-trusty-amd64.deb
# dpkg -i wkhtmltox-0.12.1_linux-trusty-amd64.deb
# cd /usr/local/bin/
# cp wkhtmltoimage /usr/bin/wkhtmltoimage
# cp wkhtmltopdf /usr/bin/wkhtmltopdf
##### 11) Tester l'installation de odoo
# su odoo
$ cd /home/odoo/odoo
$ ./odoo.py
lancer le navigateur http://localhost:8069 la page de création de base de donnée d'odoo devrait s'ouvrir, essayé de créer une base de donnée
ctrl + c pour tuer le processus odoo depuis la console
## Pour aller plus loin: init.d script
##### 1) créer un répertoire de log
# su odoo
$ mkdir /home/odoo/log
##### 2) créer fichier de config odoo
$ cd /home/odoo/odoo
$ ./odoo.py -s -c /home/odoo/odoo.conf --stop-after-init --logfile=/home/odoo/log/odoo.log
##### 3) Créer le fichier init.d
$ exit
$ vim /etc/init.d/odoo
copier le contenu dans le fichier (gedit va aussi bien que vim)
#!/bin/sh
### BEGIN INIT INFO
# Provides: openerp-server
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Should-Start: $network
# Should-Stop: $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Enterprise Resource Management software
# Description: Open ERP is a complete ERP and CRM software.
### END INIT INFO
PATH=/bin:/sbin:/usr/bin
DAEMON=/home/odoo/odoo/odoo.py
NAME=odoo
DESC=odoo
# Specify the user name (Default: openerp).
USER=odoo
# Specify an alternate config file (Default: /etc/openerp-server.conf).
CONFIGFILE="/home/odoo/odoo.conf"
# pidfile
PIDFILE=/var/run/$NAME.pid
# Additional options that are passed to the Daemon.
DAEMON_OPTS="-c $CONFIGFILE"
[ -x $DAEMON ] || exit 0
[ -f $CONFIGFILE ] || exit 0
checkpid() {
[ -f $PIDFILE ] || return 1
pid=`cat $PIDFILE`
[ -d /proc/$pid ] && return 0
pid=`cat $PIDFILE`
[ -d /proc/$pid ] && return 0
return 1
}
case "${1}" in
start)
echo -n "Starting ${DESC}: "
start-stop-daemon --start --quiet --pidfile ${PIDFILE} \
--chuid ${USER} --background --make-pidfile \
--exec ${DAEMON} -- ${DAEMON_OPTS}
echo "${NAME}."
;;
stop)
echo -n "Stopping ${DESC}: "
start-stop-daemon --stop --quiet --pidfile ${PIDFILE} \
--oknodo
echo "${NAME}."
;;
restart|force-reload)
echo -n "Restarting ${DESC}: "
start-stop-daemon --stop --quiet --pidfile ${PIDFILE} \
--oknodo
sleep 1
start-stop-daemon --start --quiet --pidfile ${PIDFILE} \
--chuid ${USER} --background --make-pidfile \
--exec ${DAEMON} -- ${DAEMON_OPTS}
echo "${NAME}."
echo "${NAME}."
;;
*)
N=/etc/init.d/${NAME}
echo "Usage: ${NAME} {start|stop|restart|force-reload}" >&2
exit 1
;;
esac
exit 0
##### 4) donner les bons droits au fichier
# chmod 755 /etc/init.d/odoo
##### 5) tester le script
# /etc/init.d/odoo start
tester à nouveau sur localhost:8069
##### 6) faire en sorte que le script s'exécute au démarrage
# update-rc.d odoo defaults
## Pour aller plus loin: proxy nginx
##### 1) installer nginx
# apt-get install nginx
vous pouvez tester l'installation réussie sur http://localhost
##### 2) configurer nginx pour odoo : editer le fichier de conf
# vim /etc/nginx/sites-enabled/default
supprimer le contenu et le remplacer par
upstream odoo {
server 127.0.0.1:8069 weight=1 fail_timeout=300s;
}
server {
# server port and name
listen 80;
server_name localhost;
location / {
proxy_pass http://odoo;
# force timeouts if the backend dies
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
# set headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
}
}
##### 3) tester la config et relancer nginx
# nginx -t
# nginx -s reload
tester http://localhost
devrait conduire à odoo (ne pas oublier de vider le cache de son navigateur au cas ou ca ne marche pas tout de suite)
##### 1) ajouter un utilisateur odoo
# adduser odoo
##### 2) installation de postgresql (DBMS)
# apt-get install postgresql
##### 3) install git
# apt-get install git
##### 4) installer pip : python package manager
# apt-get install python-pip
##### 5) installation des paquets devel pour compilation des bibliothèques python
# apt-get install python-dev postgresql-server-dev-all libjpeg-dev zlib1g-dev libpng12-dev libxml2-dev libxslt1-dev libldap2-dev libsasl2-dev
##### 6) installation de node-less
# apt-get install node-less
##### 7) clone odoo
# su odoo
$ cd /home/odoo
$ git clone https://github.com/odoo/odoo.git
##### 8) installer bibliothèque python
$ exit
# cd /home/odoo/odoo
# pip install -r requirements.txt
##### 9) créer odoo user pour postgresql avec les droits de création de base de donnée
# su postgres
$ createuser -d odoo
$ exit
##### 10) Installer wkhtml to pdf 0.12.1 !! (pas une autre) (sur une machine 64 bit avec un ubuntu 64bit 14.04)
# apt-get install fontconfig libfontconfig1 libxrender1 fontconfig-config
# wget http://download.gna.org/wkhtmltopdf/0.12/0.12.1/wkhtmltox-0.12.1_linux-trusty-amd64.deb
# dpkg -i wkhtmltox-0.12.1_linux-trusty-amd64.deb
# cd /usr/local/bin/
# cp wkhtmltoimage /usr/bin/wkhtmltoimage
# cp wkhtmltopdf /usr/bin/wkhtmltopdf
##### 11) Tester l'installation de odoo
# su odoo
$ cd /home/odoo/odoo
$ ./odoo.py
lancer le navigateur http://localhost:8069 la page de création de base de donnée d'odoo devrait s'ouvrir, essayé de créer une base de donnée
ctrl + c pour tuer le processus odoo depuis la console
## Pour aller plus loin: init.d script
##### 1) créer un répertoire de log
# su odoo
$ mkdir /home/odoo/log
##### 2) créer fichier de config odoo
$ cd /home/odoo/odoo
$ ./odoo.py -s -c /home/odoo/odoo.conf --stop-after-init --logfile=/home/odoo/log/odoo.log
##### 3) Créer le fichier init.d
$ exit
$ vim /etc/init.d/odoo
copier le contenu dans le fichier (gedit va aussi bien que vim)
#!/bin/sh
### BEGIN INIT INFO
# Provides: openerp-server
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Should-Start: $network
# Should-Stop: $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Enterprise Resource Management software
# Description: Open ERP is a complete ERP and CRM software.
### END INIT INFO
PATH=/bin:/sbin:/usr/bin
DAEMON=/home/odoo/odoo/odoo.py
NAME=odoo
DESC=odoo
# Specify the user name (Default: openerp).
USER=odoo
# Specify an alternate config file (Default: /etc/openerp-server.conf).
CONFIGFILE="/home/odoo/odoo.conf"
# pidfile
PIDFILE=/var/run/$NAME.pid
# Additional options that are passed to the Daemon.
DAEMON_OPTS="-c $CONFIGFILE"
[ -x $DAEMON ] || exit 0
[ -f $CONFIGFILE ] || exit 0
checkpid() {
[ -f $PIDFILE ] || return 1
pid=`cat $PIDFILE`
[ -d /proc/$pid ] && return 0
pid=`cat $PIDFILE`
[ -d /proc/$pid ] && return 0
return 1
}
case "${1}" in
start)
echo -n "Starting ${DESC}: "
start-stop-daemon --start --quiet --pidfile ${PIDFILE} \
--chuid ${USER} --background --make-pidfile \
--exec ${DAEMON} -- ${DAEMON_OPTS}
echo "${NAME}."
;;
stop)
echo -n "Stopping ${DESC}: "
start-stop-daemon --stop --quiet --pidfile ${PIDFILE} \
--oknodo
echo "${NAME}."
;;
restart|force-reload)
echo -n "Restarting ${DESC}: "
start-stop-daemon --stop --quiet --pidfile ${PIDFILE} \
--oknodo
sleep 1
start-stop-daemon --start --quiet --pidfile ${PIDFILE} \
--chuid ${USER} --background --make-pidfile \
--exec ${DAEMON} -- ${DAEMON_OPTS}
echo "${NAME}."
echo "${NAME}."
;;
*)
N=/etc/init.d/${NAME}
echo "Usage: ${NAME} {start|stop|restart|force-reload}" >&2
exit 1
;;
esac
exit 0
##### 4) donner les bons droits au fichier
# chmod 755 /etc/init.d/odoo
##### 5) tester le script
# /etc/init.d/odoo start
tester à nouveau sur localhost:8069
##### 6) faire en sorte que le script s'exécute au démarrage
# update-rc.d odoo defaults
## Pour aller plus loin: proxy nginx
##### 1) installer nginx
# apt-get install nginx
vous pouvez tester l'installation réussie sur http://localhost
##### 2) configurer nginx pour odoo : editer le fichier de conf
# vim /etc/nginx/sites-enabled/default
supprimer le contenu et le remplacer par
upstream odoo {
server 127.0.0.1:8069 weight=1 fail_timeout=300s;
}
server {
# server port and name
listen 80;
server_name localhost;
location / {
proxy_pass http://odoo;
# force timeouts if the backend dies
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
# set headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
}
}
##### 3) tester la config et relancer nginx
# nginx -t
# nginx -s reload
tester http://localhost
devrait conduire à odoo (ne pas oublier de vider le cache de son navigateur au cas ou ca ne marche pas tout de suite)
## Sécurité ## Sécurité
> odoo plus accessible sur le port et changer le master password
a) editer fichier de conf de odoo
# vim /home/odoo/odoo.conf
changer
admin_passwd = admin
xmlrpc_interface =
pour
admin_passwd = secret_password
xmlrpc_interface = 127.0.0.1
b) redémarrer odoo
# /etc/init.d/odoo restart
> odoo plus accessible sur le port et changer le master password
a) editer fichier de conf de odoo
# vim /home/odoo/odoo.conf
changer
admin_passwd = admin
xmlrpc_interface =
pour
admin_passwd = secret_password
xmlrpc_interface = 127.0.0.1
b) redémarrer odoo
# /etc/init.d/odoo restart

1
install-odoo-linux.md

@ -53,4 +53,3 @@ pip install --no-binary :all: psycopg2
``` ```
You should now be able to start a simple odoo instance with `./odoo/odoo.py` You should now be able to start a simple odoo instance with `./odoo/odoo.py`

2
macavrac_base/__init__.py

@ -1 +1 @@
from . import models
from . import models

26
macavrac_base/__manifest__.py

@ -1,23 +1,15 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': "Macavrac Base Module",
'summary': """
"name": "Macavrac Base Module",
"summary": """
Module with basic customizations for the Macavrac cooperative. Module with basic customizations for the Macavrac cooperative.
""", """,
'description': """
"description": """
""", """,
'author': "Patricia Daloze",
'category': 'Sales',
'version': '12.0.1.0.0',
'depends': ['beesdoo_shift', 'contacts'],
'data': [
'views/res_partner.xml',
],
'installable': True,
"author": "Patricia Daloze",
"category": "Sales",
"version": "12.0.1.0.0",
"depends": ["beesdoo_shift", "contacts"],
"data": ["views/res_partner.xml"],
"installable": True,
} }

2
macavrac_base/models/__init__.py

@ -1 +1 @@
from . import res_partner
from . import res_partner

74
macavrac_base/models/res_partner.py

@ -1,49 +1,81 @@
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class Partner(models.Model): class Partner(models.Model):
_inherit = 'res.partner'
_inherit = "res.partner"
date_stamp = fields.Date(string="Timestamp", help="Date de remplissage du formulaire")
date_stamp = fields.Date(
string="Timestamp", help="Date de remplissage du formulaire"
)
birthdate = fields.Date(string="Date d'anniversaire") birthdate = fields.Date(string="Date d'anniversaire")
payment_date = fields.Date(string="Date de paiement") payment_date = fields.Date(string="Date de paiement")
certificate_sent_date = fields.Date(string="Certificat envoyé le") certificate_sent_date = fields.Date(string="Certificat envoyé le")
fiscal_certificate_sent_date = fields.Date(string="Attestation fiscale envoyée le")
fiscal_certificate_sent_date = fields.Date(
string="Attestation fiscale envoyée le"
)
coop_number = fields.Integer(string="Coop N°") coop_number = fields.Integer(string="Coop N°")
share_qty = fields.Integer(string="Nombre de part") share_qty = fields.Integer(string="Nombre de part")
share_amount = fields.Float(string="Montant", compute="_compute_share_amount")
share_amount = fields.Float(
string="Montant", compute="_compute_share_amount"
)
gender = fields.Selection([('female','Féminin'),('male','Masculin'),('other','Autre')], string="Genre")
cooperator_type = fields.Selection([('share_a', 'Part A'), ('share_b', 'Part B'), ('share_c', 'Part C'), ('share_d', 'Part D')], string="Type de Part")
state_request = fields.Selection([('ok',"En ordre"),('waiting_payment','En attente de paiement'),
('certificate_to_send', 'Certificat à envoyer'), ('resigning', 'Parts revendues')]) #TODO should we use the cooperative.status model instead?
gender = fields.Selection(
[("female", "Féminin"), ("male", "Masculin"), ("other", "Autre")],
string="Genre",
)
cooperator_type = fields.Selection(
[
("share_a", "Part A"),
("share_b", "Part B"),
("share_c", "Part C"),
("share_d", "Part D"),
],
string="Type de Part",
)
state_request = fields.Selection(
[
("ok", "En ordre"),
("waiting_payment", "En attente de paiement"),
("certificate_to_send", "Certificat à envoyer"),
("resigning", "Parts revendues"),
]
) # TODO should we use the cooperative.status model instead?
national_register_number = fields.Char(string="Numéro de registre national") #TODO add constraint / check consistancy
national_register_number = fields.Char(
string="Numéro de registre national"
) # TODO add constraint / check consistancy
share_numbers = fields.Char(string="Numéro de parts") share_numbers = fields.Char(string="Numéro de parts")
payment_details = fields.Char(string="Détail de paiement") payment_details = fields.Char(string="Détail de paiement")
iban = fields.Char(string="IBAN") #TODO remove. Temp for import purpose.
iban = fields.Char(string="IBAN") # TODO remove. Temp for import purpose.
comment_request = fields.Char(string="Commentaire") comment_request = fields.Char(string="Commentaire")
email_sent = fields.Boolean(string="Email envoyé") email_sent = fields.Boolean(string="Email envoyé")
is_worker = fields.Boolean(compute="_compute_is_worker", search="_search_is_worker", string="is Worker", readonly=True, related="")
is_worker = fields.Boolean(
compute="_compute_is_worker",
search="_search_is_worker",
string="is Worker",
readonly=True,
related="",
)
@api.depends('share_qty')
@api.depends("share_qty")
def _compute_share_amount(self): def _compute_share_amount(self):
for rec in self: for rec in self:
rec.share_amount = rec.share_qty * 25.0 #TODO add ir.config_parameter to make this amount editable
rec.share_amount = (
rec.share_qty * 25.0
) # TODO add ir.config_parameter to make this amount editable
@api.depends('cooperator_type')
@api.depends("cooperator_type")
def _compute_is_worker(self): def _compute_is_worker(self):
for rec in self: for rec in self:
rec.is_worker = rec.cooperator_type == 'share_b'
rec.is_worker = rec.cooperator_type == "share_b"
def _search_is_worker(self, operator, value): def _search_is_worker(self, operator, value):
if (operator == '=' and value) or (operator == '!=' and not value):
return [('cooperator_type', '=', 'share_b')]
if (operator == "=" and value) or (operator == "!=" and not value):
return [("cooperator_type", "=", "share_b")]
else: else:
return [('cooperator_type', '!=', 'share_b')]
return [("cooperator_type", "!=", "share_b")]

2
purchase_order_generator/models/product_template.py

@ -3,7 +3,7 @@
# Vincent Van Rossem <vincent@coopiteasy.be> # Vincent Van Rossem <vincent@coopiteasy.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
from odoo import api, fields, models
class ProductTemplate(models.Model): class ProductTemplate(models.Model):

2
purchase_order_generator/models/purchase_order.py

@ -2,7 +2,7 @@
# Robin Keunen <robin@coopiteasy.be> # Robin Keunen <robin@coopiteasy.be>
# Vincent Van Rossem <vincent@coopiteasy.be> # Vincent Van Rossem <vincent@coopiteasy.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, SUPERUSER_ID
from odoo import SUPERUSER_ID, api, fields, models
class PurchaseOrder(models.Model): class PurchaseOrder(models.Model):

5
purchase_order_generator/models/purchase_order_generator.py

@ -3,7 +3,7 @@
# Vincent Van Rossem <vincent@coopiteasy.be> # Vincent Van Rossem <vincent@coopiteasy.be>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
@ -20,8 +20,7 @@ class PurchaseOrderGenerator(models.Model):
"converted into a purchase order.", "converted into a purchase order.",
) )
date_planned = fields.Datetime( date_planned = fields.Datetime(
string="Date Planned",
default=fields.Datetime.now,
string="Date Planned", default=fields.Datetime.now
) )
supplier_id = fields.Many2one( supplier_id = fields.Many2one(
comodel_name="res.partner", comodel_name="res.partner",

4
purchase_order_generator/models/purchase_order_generator_line.py

@ -4,7 +4,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import logging import logging
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -102,7 +102,7 @@ class PurchaseOrderGeneratorLine(models.Model):
if cpol.supplierinfo_id and cpol.supplierinfo_id.product_code: if cpol.supplierinfo_id and cpol.supplierinfo_id.product_code:
product_code = cpol.supplierinfo_id.product_code product_code = cpol.supplierinfo_id.product_code
product_name = cpol.product_template_id.name product_name = cpol.product_template_id.name
cpol_name = "[%s] %s" % (product_code, product_name)
cpol_name = "[{}] {}".format(product_code, product_name)
else: else:
cpol_name = cpol.product_template_id.name cpol_name = cpol.product_template_id.name
cpol.name = cpol_name cpol.name = cpol_name

2
purchase_order_generator/tests/test_pog.py

@ -2,7 +2,7 @@
# @author: Robin Keunen <robin@coopiteasy.be> # @author: Robin Keunen <robin@coopiteasy.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.tests.common import TransactionCase, Form
from odoo.tests.common import Form, TransactionCase
class TestCPO(TransactionCase): class TestCPO(TransactionCase):

4
website_portal_restrict_modification/controllers/main.py

@ -29,6 +29,8 @@ class CustomerPortalRestrictModification(CustomerPortal):
and any("unknown field" in s.lower() for s in error_message) and any("unknown field" in s.lower() for s in error_message)
): ):
error.pop("common") error.pop("common")
error_message = [s for s in error_message if "unknown field" not in s.lower()]
error_message = [
s for s in error_message if "unknown field" not in s.lower()
]
return error, error_message return error, error_message
Loading…
Cancel
Save