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
```
##### 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';"
@ -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;
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
# 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
""",
'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"/>
</record>
</odoo>

29
beesdoo_base/models/membercard.py

@ -1,24 +1,35 @@
from odoo import models, fields, api
import uuid
class MemberCard(models.Model):
from odoo import api, fields, models
class MemberCard(models.Model):
def _get_current_user(self):
return self.env.uid
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)
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"
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")
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
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):
for rec in self:
if rec.eater == 'eater':
if rec.eater == "eater":
rec.parent_barcode = rec.parent_eater_id.barcode
elif rec.member_card_ids:
for c in rec.member_card_ids:
@ -29,45 +47,49 @@ class Partner(models.Model):
def write(self, values):
for rec in self:
if (
values.get('parent_eater_id')
values.get("parent_eater_id")
and rec.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
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:
command[0] = 3
return super(Partner, self).write(values)
def _deactivate_active_cards(self):
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.end_date = fields.Date.today()
@api.multi
def _new_card(self, reason, user_id, barcode=False):
card_data = {
'partner_id' : self.id,
'responsible_id' : user_id,
'comment' : reason,
"partner_id": self.id,
"responsible_id": user_id,
"comment": reason,
}
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
def _new_eater(self, surname, name, email):
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):
"""
@ -6,49 +7,61 @@ class NewMemberCardWizard(models.TransientModel):
The user can only define the raison why a new card is
needed and the eater/worker that is concerned.
"""
_name = 'membercard.new.wizard'
_name = "membercard.new.wizard"
_description = "Member Card"
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
def create_new_card(self):
client = self.partner_id.sudo()
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
class RequestMemberCardPrintingWizard(models.TransientModel):
_name = 'membercard.requestprinting.wizard'
_name = "membercard.requestprinting.wizard"
_description = "Member Card - Request Print Wizard"
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
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):
_name = 'membercard.set_as_printed.wizard'
_name = "membercard.set_as_printed.wizard"
_description = "Member card - Set as printed wizard"
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
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):
"""
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):
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
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):
_inherit = 'portal.wizard'
_inherit = "portal.wizard"
@api.onchange('portal_id')
@api.onchange("portal_id")
def onchange_portal(self):
# 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()
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
if contact.id not in contact_ids:
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).
{
'name': "Beescoop Crelan Import module",
'summary': """
"name": "Beescoop Crelan Import module",
"summary": """
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):
formats_list = super()._get_bank_statements_available_import_formats()
formats_list.append('Crelan')
formats_list.append("Crelan")
return formats_list

129
beesdoo_crelan_csv/wizard/import_crelan_csv.py

@ -1,8 +1,9 @@
from io import StringIO
import csv
import datetime
import hashlib
from odoo import models, _
from io import StringIO
from odoo import _, models
ACCOUNT = "Compte donneur d'ordre"
CURRENCY = "Devise"
@ -13,83 +14,125 @@ COUNTERPART_NAME = "Contrepartie"
COMMUNICATION = "Communication"
TRANSACTION_TYPE = "Type d'opération"
class CodaBankStatementImport(models.TransientModel):
_inherit = 'account.bank.statement.import'
_inherit = "account.bank.statement.import"
_date_format = "%d/%m/%Y"
_decimal_sep = "."
_csv_delimiter = ";"
_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):
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):
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
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 = {
'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
def _get_acc_number_crelan(self, 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
return acc_number
return journal.bank_acc_number
def _get_acc_balance_crelan(self, acc_number):
if not self.init_balance == None:
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
if not journal or len(journal) > 1: # If not found or ambiguious
self.init_balance = 0.0
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
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):
try:
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:
raise ValueError()
except ValueError:
@ -113,6 +156,12 @@ class CodaBankStatementImport(models.TransientModel):
transactions.append(self._get_move_value_crelan(statement, i))
sum_transaction += float(statement[AMOUNT])
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
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.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):
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

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

20
beesdoo_easy_my_coop/models/res_company.py

@ -1,12 +1,12 @@
# Copyright 2019 Coop IT Easy SCRLfs
# 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):
_inherit = 'res.company'
_inherit = "res.company"
display_info_session_confirmation = fields.Boolean(
help="Choose to display a info session checkbox on the cooperator"
" website form."
@ -17,19 +17,21 @@ class ResCompany(models.Model):
info_session_confirmation_text = fields.Html(
translate=True,
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):
if self.info_session_confirmation_required:
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)
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
# 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
class Partner(models.Model):
_inherit = 'res.partner'
_inherit = "res.partner"
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(
compute="_is_worker",
search="_search_worker",
readonly=True,
related=""
related="",
)
def _cooperator_share_type(self):
@ -27,16 +26,17 @@ class Partner(models.Model):
share_type = None
if self.cooperator_type:
share_type = (
self.env['product.template']
.search([('default_code', '=', self.cooperator_type)])
self.env["product.template"].search(
[("default_code", "=", self.cooperator_type)]
)
)[0]
return share_type
@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):
"""
@ -53,7 +53,7 @@ class Partner(models.Model):
rec.worker_store = False
def _search_worker(self, operator, value):
return [('worker_store', operator, value)]
return [("worker_store", operator, value)]
@api.depends(
"cooperative_status_ids",
@ -79,10 +79,11 @@ class Partner(models.Model):
else:
rec.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):
"""
Check that the parent_eater_id in parnter in self doesn't exceed
@ -95,16 +96,15 @@ class Partner(models.Model):
if (
share_type
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(
_('You can only set %d additional eaters per worker')
_("You can only set %d additional eaters per worker")
% share_type.max_nb_eater_allowed
)
@api.constrains('child_eater_ids')
@api.constrains("child_eater_ids")
def _check_max_child_eaters(self):
"""
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
):
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
)

13
beesdoo_easy_my_coop/models/subscription_request.py

@ -1,26 +1,25 @@
# Copyright 2019 Coop IT Easy SCRLfs
# 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):
_inherit = 'subscription.request'
_inherit = "subscription.request"
info_session_confirmed = fields.Boolean(
string="Confirmed Info Session",
default=False,
string="Confirmed Info Session", default=False
)
def get_partner_vals(self):
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
def get_required_field(self):
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:
required_fields.append('info_session_confirmed')
required_fields.append("info_session_confirmed")
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
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)]})
self.assertEqual(len(coop1.child_eater_ids), 1)
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
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)]})
self.assertEqual(len(coop2.child_eater_ids), 1)
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.
"""
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)]})
self.assertEqual(len(coop3.child_eater_ids), 1)
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.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:
coop3.write({"child_eater_ids": [(4, self.eater3.id)]})
self.assertIn("can only set", str(econtext.exception))
@ -108,9 +100,7 @@ class TestResPartner(TransactionCase):
"""
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(
{
"child_eater_ids": [
@ -126,9 +116,7 @@ class TestResPartner(TransactionCase):
"""
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.eater2
eaters |= self.eater3
@ -139,9 +127,7 @@ class TestResPartner(TransactionCase):
"""
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
coop1._is_worker()
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.
"""
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
coop2._is_worker()
self.assertEqual(coop2.is_worker, False)
@ -162,12 +146,8 @@ class TestResPartner(TransactionCase):
Test that the search function returns worker based on the
'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
coop1._is_worker()
coop2._is_worker()
@ -182,14 +162,12 @@ class TestResPartner(TransactionCase):
"""
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
coop1._compute_can_shop()
self.assertEqual(coop1.can_shop, True)
# 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.can_shop, False)
@ -197,9 +175,7 @@ class TestResPartner(TransactionCase):
"""
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
coop3._compute_can_shop()
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
# 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):
_inherit = 'beesdoo.shift.subscribe'
_inherit = "beesdoo.shift.subscribe"
def _get_info_session_followed(self):
"""
@ -15,10 +15,11 @@ class Subscribe(models.TransientModel):
"""
followed = super(Subscribe, self)._get_info_session_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
info_session = fields.Boolean(default=_get_info_session_followed)

25
beesdoo_inventory/__manifest__.py

@ -5,23 +5,18 @@
# - Jean-Marc François
# 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
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):
_inherit = 'stock.picking'
_inherit = "stock.picking"
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):
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
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
# 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
""",
'description': """
"description": """
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):
_inherit = 'res.partner'
_inherit = "res.partner"
def _get_eater(self):
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 {
height: 108px;
}
}

2
beesdoo_pos_reporting/models/res_partner.py

@ -4,4 +4,4 @@ from odoo import fields, models
class ResPartner(models.Model):
_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
# 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
- 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
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.tools.translate import _
class BeesdooProduct(models.Model):
_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
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_consumption = fields.Integer(string="Deadline for consumption(days)")
deadline_for_consumption = fields.Integer(
string="Deadline for consumption(days)"
)
ingredients = fields.Char(string="Ingredient")
scale_label_info_1 = fields.Char(string="Scale lable info 1")
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
def _get_scale_sale_uom(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):
suppliers = self.seller_ids.sorted(
key=lambda seller: seller.date_start,
reverse=True)
key=lambda seller: seller.date_start, reverse=True
)
if suppliers:
return suppliers[0]
else:
@ -62,84 +96,140 @@ class BeesdooProduct(models.Model):
@api.one
def generate_barcode(self):
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:
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()
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()
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:
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)
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
while(self.search_count([('barcode', '=', bc)]) > 1):
while self.search_count([("barcode", "=", bc)]) > 1:
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
@api.one
@api.depends('seller_ids', 'seller_ids.date_start')
@api.depends("seller_ids", "seller_ids.date_start")
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
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
@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):
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:
self.total_with_vat = self.list_price
return True
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():
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:
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_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:
self.total_with_vat_by_unit = self.total_with_vat / self.weight
@api.one
@api.depends('weight', 'display_unit')
@api.depends("weight", "display_unit")
def _get_display_weight(self):
self.display_weight = self.weight * self.display_unit.factor
@api.one
@api.constrains('display_unit', 'default_reference_unit')
@api.constrains("display_unit", "default_reference_unit")
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.depends('seller_ids')
@api.depends("seller_ids")
def _compute_cost(self):
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):
_name = "beesdoo.scale.category"
@ -147,43 +237,64 @@ class BeesdooScaleCategory(models.Model):
name = fields.Char(string="Scale name category")
code = fields.Integer(string="Category code")
_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):
_name = "beesdoo.product.label"
_description = "beesdoo.product.label"
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()
active = fields.Boolean(default=True)
class BeesdooProductCategory(models.Model):
_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.constrains('profit_margin')
@api.constrains("profit_margin")
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):
_inherit = "product.supplierinfo"
price = fields.Float('exVAT Price')
price = fields.Float("exVAT Price")
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):
_name = 'label.printing.wizard'
_description = 'label.printing.wizard'
_name = "label.printing.wizard"
_description = "label.printing.wizard"
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
def request_printing(self):
self.product_ids.write({'label_to_be_printed' : True})
self.product_ids.write({"label_to_be_printed": True})
@api.one
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
# 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):
_inherit = "product.template"
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(
string='Price',
compute='_compute_main_supplierinfo',
string="Price", compute="_compute_main_supplierinfo"
)
main_minimum_qty = fields.Float(
string='Minimum Quantity',
compute='_compute_main_supplierinfo',
string="Minimum Quantity", compute="_compute_main_supplierinfo"
)
@api.multi
@api.depends('seller_ids')
@api.depends("seller_ids")
def _compute_main_supplierinfo(self):
for product in self:
supplierinfo = product._get_main_supplier_info()

5
beesdoo_purchase/__manifest__.py

@ -18,8 +18,5 @@
"category": "Purchase",
"version": "12.0.1.1.0",
"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/cron.xml",
"data/mail_template.xml",
@ -38,8 +32,6 @@
"wizard/holiday.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 planning
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
from datetime import datetime, timedelta
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
_logger = logging.getLogger(__name__)
def add_days_delta(date_from, days_delta):
if not date_from:
return date_from
next_date = date_from + timedelta(days=days_delta)
return next_date
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)
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()
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):
_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
def _get_status(self):
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")
sr = fields.Integer("Regular 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_end_time = fields.Date("Holidays End Day")
alert_start_time = fields.Date("Alert Start Day")
extension_start_time = fields.Date("Extension Start Day")
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")
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_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_end_date = fields.Date()
@api.depends('status')
@api.depends("status")
def _compute_can_shop(self):
for rec in self:
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):
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:
if update or not rec.today:
rec.status = 'ok'
rec.status = "ok"
continue
if rec.resigning:
rec.status = 'resigning'
rec.status = "resigning"
continue
if rec.working_mode == 'regular':
if rec.working_mode == "regular":
rec.status = rec._get_regular_status()
elif rec.working_mode == 'irregular':
elif rec.working_mode == "irregular":
rec.status = rec._get_irregular_status()
elif rec.working_mode == 'exempt':
rec.status = 'ok'
elif rec.working_mode == "exempt":
rec.status = "ok"
_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")
def _constrains_irregular_start_date(self):
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
def write(self, vals):
"""
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:
continue
for rec in self:
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]:
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)
@api.multi
@ -152,28 +213,42 @@ class CooperativeStatus(models.Model):
Overwrite write to historize the change of status
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()
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:
if old_status_per_id[rec.id]['status'] != vals['status']:
if old_status_per_id[rec.id]["status"] != vals["status"]:
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)
def get_status_value(self):
"""
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]
@api.model
@ -181,7 +256,7 @@ class CooperativeStatus(models.Model):
"""
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
def _cron_compute_counter_irregular(self, today=False):
@ -190,15 +265,21 @@ class CooperativeStatus(models.Model):
once per day
"""
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:
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)
irregular = self.search(domain)
for status in irregular:
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()
journal.line_ids |= status
@ -217,7 +298,7 @@ class CooperativeStatus(models.Model):
##############################
# Computed field section #
##############################
@api.depends('today')
@api.depends("today")
def _compute_future_alert_date(self):
"""
Compute date until the worker is up to date
@ -226,7 +307,7 @@ class CooperativeStatus(models.Model):
for rec in self:
rec.future_alert_date = False
@api.depends('today')
@api.depends("today")
def _compute_next_countdown_date(self):
"""
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
to active cooperator privilege
"""
return ['ok', 'alert', 'extension', 'exempted']
return ["ok", "alert", "extension", "exempted"]
#####################################
# Status Change implementation #
@ -253,14 +334,14 @@ class CooperativeStatus(models.Model):
Return the value of the status
for the regular worker
"""
return 'ok'
return "ok"
def _get_irregular_status(self):
"""
Return the value of the status
for the irregular worker
"""
return 'ok'
return "ok"
def _state_change(self, new_state):
"""
@ -275,7 +356,6 @@ class CooperativeStatus(models.Model):
"""
pass
###############################################
###### Irregular Cron implementation ##########
###############################################
@ -286,7 +366,7 @@ class CooperativeStatus(models.Model):
of valid irregular worker that should
get their counter changed by the cron
"""
return [(0, '=', 1)]
return [(0, "=", 1)]
def _change_irregular_counter(self):
"""
@ -297,22 +377,31 @@ class CooperativeStatus(models.Model):
"""
pass
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()
line_ids = fields.Many2many('cooperative.status')
line_ids = fields.Many2many("cooperative.status")
_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
def run(self):
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
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):
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):
decimal, integer = math.modf(f)
return int(integer), int(round(decimal * 60))
def get_first_day_of_week():
today = datetime.now()
return (datetime.now() - timedelta(days=today.weekday())).date()
class TaskType(models.Model):
_name = 'beesdoo.shift.type'
_description = 'beesdoo.shift.type'
_name = "beesdoo.shift.type"
_description = "beesdoo.shift.type"
name = fields.Char()
description = fields.Text()
active = fields.Boolean(default=True)
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()
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)
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()
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
def _get_next_planning(self, sequence):
next_planning = self.search([('sequence', '>', sequence)])
next_planning = self.search([("sequence", ">", sequence)])
if not next_planning:
return self.search([])[0]
return next_planning[0]
@ -56,15 +69,15 @@ class Planning(models.Model):
@api.multi
def _get_next_planning_date(self, date):
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)
@api.model
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(
config.get_param('next_planning_date', 0)
config.get_param("next_planning_date", 0)
)
planning = self._get_next_planning(last_seq)
@ -72,49 +85,70 @@ class Planning(models.Model):
planning.task_template_ids._generate_task_day()
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):
_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)
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)
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")
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)
#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")
#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):
"""Combine day number, hours and minutes to save
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 = context_tz.localize(day_local_time)
day_utc_time = day_local_time.astimezone(UTC)
# 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):
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:
# Find the day of this task template 'rec'.
day = today + timedelta(days=rec.day_nb_id.number - 1)
@ -128,84 +162,109 @@ class TaskTemplate(models.Model):
def _dummy_search(self, operator, value):
return []
@api.depends('worker_ids', 'worker_nb')
@api.depends("worker_ids", "worker_nb")
def _get_remaining(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")
def _get_worker_name(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):
for rec in self:
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):
if self.start_time and self.end_time:
self.duration = self.end_time - self.start_time
@api.onchange('duration')
@api.onchange("duration")
def _set_duration(self):
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):
tasks = self.env['beesdoo.shift.shift']
tasks = self.env["beesdoo.shift.shift"]
for rec in self:
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:
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
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
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
@api.onchange('worker_ids')
@api.onchange("worker_ids")
def check_for_multiple_shifts(self):
original_ids = {worker.id for worker in self._origin.worker_ids}
warnings = []
for worker in self.worker_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:
warnings.append(
worker.name + _(' is already assigned to ') + ", ".join(shifts))
worker.name
+ _(" is already assigned to ")
+ ", ".join(shifts)
)
if warnings:
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
from datetime import datetime, timedelta
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
class ResPartner(models.Model):
@ -10,20 +10,59 @@ class ResPartner(models.Model):
One2many relationship with CooperativeStatus should
be replaced by inheritance.
"""
_inherit = 'res.partner'
_inherit = "res.partner"
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")
def _compute_can_shop(self):
@ -41,58 +80,58 @@ class ResPartner(models.Model):
@api.multi
def coop_subscribe(self):
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
def coop_unsubscribe(self):
res = self.coop_subscribe()
res['context'] = {'default_unsubscribed': True}
res["context"] = {"default_unsubscribed": True}
return res
@api.multi
def manual_extension(self):
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
def auto_extension(self):
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
@api.multi
def register_holiday(self):
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
def temporary_exempt(self):
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
class Task(models.Model):
_name = 'beesdoo.shift.shift'
_name = "beesdoo.shift.shift"
_inherit = ['mail.thread']
_inherit = ["mail.thread"]
_order = "start_time asc"
@ -20,11 +19,11 @@ class Task(models.Model):
##################################
def _get_selection_status(self):
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):
@ -40,40 +39,57 @@ class Task(models.Model):
def _get_final_state(self):
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",
required=True,
track_visibility='onchange',
group_expand='_expand_states'
track_visibility="onchange",
group_expand="_expand_states",
)
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_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)
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):
return [key for key, val in self._fields['state'].selection]
return [key for key, val in self._fields["state"].selection]
@api.depends("state")
def _compute_color(self):
@ -85,8 +101,9 @@ class Task(models.Model):
Raise a validation error if the fields is_regular and
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(
"You must choose between Regular Shift or "
"Compensation Shift."
@ -96,18 +113,20 @@ class Task(models.Model):
def _lock_future_task(self):
if datetime.now() < self.start_time:
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):
for task in self:
if task.working_mode == 'regular':
if task.working_mode == "regular":
self._compensation_validation(task)
@api.constrains('worker_id')
@api.constrains("worker_id")
def _check_worker_id(self):
"""
When worker_id changes we need to check whether is_regular
@ -117,29 +136,30 @@ class Task(models.Model):
False.
"""
for task in self:
if task.working_mode == 'regular':
if task.working_mode == "regular":
self._compensation_validation(task)
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 == task.replaced_id:
raise UserError("A worker cannot replace himself.")
def message_auto_subscribe(self, updated_fields, values=None):
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):
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)
#TODO button to replaced someone
# TODO button to replaced someone
@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.
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 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:
today = today or fields.Date.today()
today = datetime.combine(today, time())
date_domain = [('start_time', '>', today)]
date_domain = [("start_time", ">", today)]
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
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
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:
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
def write(self, vals):
@ -187,56 +219,82 @@ class Task(models.Model):
Change the worker info
Compute state change for the new worker
"""
if 'worker_id' in vals:
if "worker_id" in vals:
for rec in self:
if rec.worker_id.id != vals['worker_id']:
if rec.worker_id.id != vals["worker_id"]:
rec._revert()
# To satisfy the constrains on worker_id, it must be
# accompanied by the change in is_regular and
# 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)
if 'state' in vals:
if "state" in vals:
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)
def _set_revert_info(self, data, status):
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):
if not self.revert_info:
return
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
def _update_state(self, new_state):
self.ensure_one()
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):
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)
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):
_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
def write_super_coop(self):
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
@author: Thibault Francois
'''
"""
from odoo import _, api, fields, models
from odoo import models, fields, api, _
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
def generate(self):
@ -22,33 +33,36 @@ class GenerateShiftTemplate(models.TransientModel):
for day in self.day_ids:
for line in self.line_ids:
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)
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):
_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)
end_time = fields.Float(required=True)
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
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):
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)
extension_days = fields.Integer(default=_get_default_extension_delay)
@api.multi
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
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:
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:
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
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)")
@api.multi
def holidays(self):
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):
_name = 'beesddoo.shift.generate_planning'
_description = 'beesddoo.shift.generate_planning'
_name = "beesddoo.shift.generate_planning"
_description = "beesddoo.shift.generate_planning"
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
def generate_task(self):
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()
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
class StatusActionMixin(models.AbstractModel):
_name = "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()
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"))
return self.with_context(real_uid=self._uid)
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):
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:
return fields.Date.today()
else:
return date
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():
return date
else:
return False
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
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:
return shifts[0]
return
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):
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):
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):
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)
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_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
def unsubscribe(self):
@ -88,53 +147,57 @@ class Subscribe(models.TransientModel):
if not self.unsubscribed:
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 = {
'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:
status_id.sudo().write(data)
else:
self.env['cooperative.status'].sudo().create(data)
self.env["cooperative.status"].sudo().create(data)
@api.multi
def subscribe(self):
self = self._check()
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:
#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 = {
'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:
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:
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(
[('cooperator_id', '=', self.cooperator_id.id)])
[("cooperator_id", "=", self.cooperator_id.id)]
)
if status_id:
status_id.sudo().write(data)
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
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)
@api.multi
def exempt(self):
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
# that implement the worker_status rules.
{
'name': "Beescoop Shift Attendance Sheet",
'summary': """
"name": "Beescoop Shift Attendance Sheet",
"summary": """
Volonteer Timetable Management
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/cron.xml",
"data/mail_template.xml",
@ -39,7 +33,5 @@
"wizard/generate_missing_attendance_sheets.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
when class is Abstract.
"""
_name = "beesdoo.shift.sheet.shift"
_description = "Copy of an actual shift into an attendance sheet"
_order = "task_type_id, worker_name"
@ -61,7 +62,9 @@ class AttendanceSheetShift(models.Model):
)
worker_name = fields.Char(related="worker_id.name", store=True)
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(
related="worker_id.working_mode", string="Working Mode"
@ -120,10 +123,7 @@ class AttendanceSheetShiftAdded(models.Model):
class AttendanceSheet(models.Model):
_name = "beesdoo.shift.sheet"
_inherit = [
"mail.thread",
"barcodes.barcode_events_mixin",
]
_inherit = ["mail.thread", "barcodes.barcode_events_mixin"]
_description = "Attendance sheet"
_order = "start_time"
@ -136,7 +136,7 @@ class AttendanceSheet(models.Model):
)
active = fields.Boolean(string="Active", default=1)
state = fields.Selection(
[("not_validated", "Not Validated"), ("validated", "Validated"),],
[("not_validated", "Not Validated"), ("validated", "Validated")],
string="State",
readonly=True,
index=True,
@ -175,9 +175,7 @@ class AttendanceSheet(models.Model):
help="Indicative maximum number of workers.",
)
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",
@ -233,9 +231,7 @@ class AttendanceSheet(models.Model):
start_time = fields.Datetime.context_timestamp(rec, rec.start_time)
end_time = fields.Datetime.context_timestamp(rec, rec.end_time)
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")
@ -319,10 +315,14 @@ class AttendanceSheet(models.Model):
)
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(
_("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":
@ -370,7 +370,9 @@ class AttendanceSheet(models.Model):
)
if worker.working_mode not in ("regular", "irregular"):
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)
)
@ -428,7 +430,11 @@ class AttendanceSheet(models.Model):
for task in tasks:
# 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(
{
"attendance_sheet_id": new_sheet.id,
@ -468,7 +474,8 @@ class AttendanceSheet(models.Model):
if expected_shift.state != "done":
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)
@ -609,7 +616,9 @@ class AttendanceSheet(models.Model):
sheets = self.env["beesdoo.shift.sheet"]
current_time = datetime.now()
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"
)
)

4
beesdoo_shift_attendance/models/res_config_settings.py

@ -3,7 +3,7 @@
import ast
from odoo import fields, models, api
from odoo import api, fields, models
class ResConfigSettings(models.TransientModel):
@ -46,6 +46,6 @@ class ResConfigSettings(models.TransientModel):
self.env["ir.config_parameter"].get_param(
"beesdoo_shift_attendance.pre_filled_task_type_id"
)
),
)
)
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_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()
@ -67,12 +69,10 @@ class TestBeesdooShift(TransactionCase):
)
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(
"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
@ -207,9 +207,7 @@ class TestBeesdooShift(TransactionCase):
# Test consistency with actual shift for sheet 1
for shift in sheet_1.expected_shift_ids:
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.super_coop_id, shift.task_id.super_coop_id)
self.assertEqual(shift.working_mode, shift.task_id.working_mode)
@ -239,7 +237,7 @@ class TestBeesdooShift(TransactionCase):
self.attendance_sheet_model.sudo(
self.user_generic
)._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)
"""
@ -352,7 +350,9 @@ class TestBeesdooShift(TransactionCase):
if waiting_time > 0:
with self.assertRaises(UserError) as econtext:
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)
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 odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
class GenerateMissingAttendanceSheets(models.TransientModel):
"""
@ -34,7 +34,7 @@ class GenerateMissingAttendanceSheets(models.TransientModel):
start_time = task.start_time
end_time = task.end_time
sheet = sheets.search(
[("start_time", "=", start_time), ("end_time", "=", end_time),]
[("start_time", "=", start_time), ("end_time", "=", end_time)]
)
if not sheet:

7
beesdoo_shift_attendance/wizard/validate_attendance_sheet.py

@ -1,4 +1,3 @@
import ast
from odoo import _, api, exceptions, fields, models
@ -21,9 +20,9 @@ class ValidateAttendanceSheet(models.TransientModel):
def _get_card_support_setting(self):
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

10
beesdoo_stock/__manifest__.py

@ -4,17 +4,13 @@
{
"name": "BEES coop Stock",
"version": "12.0.1.0.0",
"depends": [
'stock',
],
"depends": ["stock"],
"author": "Coop IT Easy SCRLfs",
"license": "AGPL-3",
"website": "www.coopiteasy.be",
"description": """
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):
_inherit = 'stock.picking'
_inherit = "stock.picking"
@api.multi
def actions_on_articles(self):
ids = self._ids
context = self._context
ctx = (context or {}).copy()
ctx['articles'] = []
ctx["articles"] = []
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 {
'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>
# 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

1
beesdoo_stock_coverage/readme/CONTRIBUTORS.rst

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

5
beesdoo_stock_coverage/tests/test_stock_coverage.py

@ -3,10 +3,11 @@
# @author: Robin Keunen <robin@coopiteasy.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import timedelta
from odoo import fields
from odoo.tools import float_compare
from odoo.tests.common import TransactionCase
from datetime import timedelta
from odoo.tools import float_compare
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).
{
'name': 'BEES coop Website Eater',
'summary': """
"name": "BEES coop Website Eater",
"summary": """
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):
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
values.update({
'eaters': partner.child_eater_ids,
})
values.update({"eaters": partner.child_eater_ids})
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).
from odoo.addons.portal.controllers.portal import CustomerPortal
from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal
class PortalPosOrderAmount(CustomerPortal):
def _prepare_portal_layout_values(self):
values = super(
PortalPosOrderAmount, self
)._prepare_portal_layout_values()
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(
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).
{
'name': 'BEES coop Website Shift',
'summary': """
"name": "BEES coop Website Shift",
"summary": """
Show available shifts for regular and irregular workers on the
website and let workers manage their shifts with an
easy web interface.
""",
'description': """
"description": """
""",
"author": "Coop IT Easy SCRLfs",
"license": "AGPL-3",

355
beesdoo_website_shift/controllers/main.py

@ -6,39 +6,41 @@
from ast import literal_eval
from datetime import datetime, timedelta
from itertools import groupby
from pytz import timezone, utc
from odoo import http, fields
from odoo import fields, http
from odoo.http import request
from odoo.addons.beesdoo_shift.models.planning import float_to_time
class WebsiteShiftController(http.Controller):
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
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
return working_mode == 'irregular'
return working_mode == "irregular"
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
return working_mode == 'regular'
return working_mode == "regular"
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):
user = request.env['res.users'].browse(request.uid)
user = request.env["res.users"].browse(request.uid)
working_mode = user.partner_id.working_mode
return working_mode == 'exempt'
return working_mode == "exempt"
def user_can_subscribe(self, user=None):
"""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
"""
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):
"""
@ -66,12 +70,12 @@ class WebsiteShiftController(http.Controller):
assert datetime.tzinfo is None
# Get current user and user timezone
# 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
if 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
dt_utc = utc.localize(datetime, is_dst=False)
# Convert to user TZ
@ -86,43 +90,39 @@ class WebsiteShiftController(http.Controller):
newdt_utc = newdt_local.astimezone(utc)
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):
"""
Personal page for managing your shifts
"""
if self.is_user_irregular():
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():
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():
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():
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():
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):
"""
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
"""
# Get current user
cur_user = request.env['res.users'].browse(request.uid)
cur_user = request.env["res.users"].browse(request.uid)
# 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
irregular_enable_sign_up = request.website.irregular_enable_sign_up
# Set start time limit as defined in beesdoo_shift settings
# TODO: Move this into the attendance_sheet module
# setting = request.website.attendance_sheet_generation_interval
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
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):
"""
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
# Create 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(
'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):
"""
Show a public access page that show all the available shift templates for regular worker.
"""
# 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
regular_highlight_rule = request.website.regular_highlight_rule
task_tpls_data = []
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))
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=""):
@ -218,16 +219,18 @@ class WebsiteShiftController(http.Controller):
template_context.update(self.my_shift_worker_status())
template_context.update(self.my_shift_next_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
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
# TODO: move this to the attendance_sheet module
@ -235,7 +238,7 @@ class WebsiteShiftController(http.Controller):
# request.website.attendance_sheet_generation_interval
# )
subscription_time_limit = 0
template_context['subscription_time_limit'] = subscription_time_limit
template_context["subscription_time_limit"] = subscription_time_limit
return template_context
@ -253,17 +256,16 @@ class WebsiteShiftController(http.Controller):
template_context = {}
# 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_next_shifts())
template_context.update(self.my_shift_past_shifts())
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
@ -273,29 +275,42 @@ class WebsiteShiftController(http.Controller):
"""
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
'beesdoo_website_shift.available_shift_irregular_worker' template
"""
# 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
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
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
@ -308,7 +323,7 @@ class WebsiteShiftController(http.Controller):
# task_type
groupby_iter = groupby(
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 = []
@ -321,29 +336,39 @@ class WebsiteShiftController(http.Controller):
free_space = len(shift_list)
# Is the current user subscribed to this task_template
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
# 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:
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
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
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):
@ -351,13 +376,19 @@ class WebsiteShiftController(http.Controller):
Return template variables for 'beesdoo_website_shift.my_shift_next_shifts' template
"""
# 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
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
subscribed_shifts = []
@ -372,22 +403,34 @@ class WebsiteShiftController(http.Controller):
if nb_subscribed_shifts > 0:
main_shift = subscribed_shifts[-1]
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
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):
# Create the fictive shift
@ -405,19 +448,17 @@ class WebsiteShiftController(http.Controller):
shift.revert_info = main_shift.revert_info
# Set new date
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(
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
subscribed_shifts.append(shift)
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):
@ -425,7 +466,7 @@ class WebsiteShiftController(http.Controller):
Return template variables for 'beesdoo_website_shift.my_shift_past_shifts' template
"""
# Get current user
cur_user = request.env['res.users'].browse(request.uid)
cur_user = request.env["res.users"].browse(request.uid)
# Get config
past_shift_limit = 0
if self.is_user_irregular():
@ -435,28 +476,44 @@ class WebsiteShiftController(http.Controller):
# Get shifts where user was subscribed
now = datetime.now()
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:
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):
"""
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).
from ast import literal_eval
from odoo import fields, models, api
from odoo import api, fields, models
class WebsiteShiftConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
_inherit = "res.config.settings"
# Irregular worker settings
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(
related='website_id.highlight_rule_pc',
readonly=False,
related="website_id.highlight_rule_pc", readonly=False
)
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(
related='website_id.irregular_enable_sign_up',
readonly=False,
related="website_id.irregular_enable_sign_up", readonly=False
)
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_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(
related='website_id.regular_next_shift_limit',
readonly=False,
related="website_id.regular_next_shift_limit", readonly=False
)
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):
_inherit = 'website'
_inherit = "website"
# Irregular worker settings
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(
default=30,
help="Treshold (in %) of available space in a shift that trigger the "
"highlight of the shift"
"highlight of the shift",
)
hide_rule = fields.Integer(
default=20,
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(
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(
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_past_shift_limit = fields.Integer(
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(
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(
default=20,
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.
""",
'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
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):
_inherit = 'cooperative.status'
_inherit = "cooperative.status"
_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):
"""Compute date before which the worker is up to date"""
for rec in self:
# 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
# Alert start time already set
elif rec.alert_start_time:
@ -35,8 +45,9 @@ class CooperativeStatus(models.Model):
elif bool(rec.holiday_start_time) != bool(rec.holiday_end_time):
rec.future_alert_date = False
# 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
else:
date = rec.today
@ -48,7 +59,8 @@ class CooperativeStatus(models.Model):
)
# Check holidays
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_end_time
):
@ -70,9 +82,14 @@ class CooperativeStatus(models.Model):
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):
"""
Compute the following countdown date. This date is the date when
@ -82,14 +99,18 @@ class CooperativeStatus(models.Model):
"""
for rec in self:
# 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
# Holidays are not set properly
elif bool(rec.holiday_start_time) != bool(rec.holiday_end_time):
rec.next_countdown_date = False
# 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
else:
date = rec.today
@ -100,7 +121,8 @@ class CooperativeStatus(models.Model):
)
# Check holidays
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_end_time
):
@ -124,28 +146,60 @@ class CooperativeStatus(models.Model):
#####################################
def _get_regular_status(self):
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
grace_delay = grace_delay + self.time_extension
if (self.sr + self.sc) <= counter_unsubscribe or self.unsubscribed:
return 'unsubscribed'
return "unsubscribed"
# 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
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):
return 'alert'
return "alert"
if (
self.holiday_start_time
@ -154,32 +208,64 @@ class CooperativeStatus(models.Model):
and self.today <= self.holiday_end_time
):
return 'holiday'
return "holiday"
elif ok or (not self.alert_start_time and self.sr >= 0):
return 'ok'
return "ok"
def _get_irregular_status(self):
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
grace_delay = grace_delay + self.time_extension
if self.sr <= counter_unsubscribe or self.unsubscribed:
return 'unsubscribed'
return "unsubscribed"
# 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
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):
return 'alert'
return "alert"
elif (
self.holiday_start_time
@ -187,29 +273,36 @@ class CooperativeStatus(models.Model):
and self.today >= self.holiday_start_time
and self.today <= self.holiday_end_time
):
return 'holiday'
return "holiday"
elif ok or (not self.alert_start_time and self.sr >= 0):
return 'ok'
return "ok"
def _state_change(self, new_state):
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)
if new_state == 'unsubscribed' or new_state == 'resigning':
if new_state == "unsubscribed" or new_state == "resigning":
# Remove worker from task_templates
self.cooperator_id.sudo().write(
{'subscribed_shift_ids': [(5, 0, 0)]})
{"subscribed_shift_ids": [(5, 0, 0)]}
)
# 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)
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()
)
@ -218,10 +311,12 @@ class CooperativeStatus(models.Model):
Call when a shift state is changed
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 ##########
@ -229,15 +324,20 @@ class CooperativeStatus(models.Model):
def _get_irregular_worker_domain(self, **kwargs):
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):

44
beesdoo_worker_status/models/task.py

@ -6,7 +6,7 @@ from odoo.exceptions import UserError, ValidationError
class Task(models.Model):
_inherit = 'beesdoo.shift.shift'
_inherit = "beesdoo.shift.shift"
#################################
# State Definition #
@ -14,12 +14,12 @@ class Task(models.Model):
def _get_selection_status(self):
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):
@ -43,7 +43,7 @@ class Task(models.Model):
##############################################
def _get_counter_date_state_change(self, new_state):
data = {}
if self.worker_id.working_mode == 'regular':
if self.worker_id.working_mode == "regular":
if not self.replaced_id: # No replacement case
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:
# Regular counter is always updated first
if status.sr < 0:
data['sr'] = 1
data["sr"] = 1
elif status.sc < 0:
data['sc'] = 1
data["sc"] = 1
# Bonus shift case
else:
data['sr'] = 1
data["sr"] = 1
if new_state == "absent_2":
data['sr'] = -1
data['sc'] = -1
data["sr"] = -1
data["sc"] = -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]
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":
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

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`.
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.
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).
```bash
docker-compose rm db
```
## 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
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.
To do that run
To do that run
```bash
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.
```bash
docker-compose up -d db
docker-compose up odoo
```
docker-compose up -d db
docker-compose up odoo
```
## 5) Login
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
> by Thibault François
> by Thibault François
## 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é
> 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`

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).
{
'name': "Macavrac Base Module",
'summary': """
"name": "Macavrac Base Module",
"summary": """
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
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")
payment_date = fields.Date(string="Date de paiement")
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°")
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")
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")
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):
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):
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):
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:
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>
# 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):

2
purchase_order_generator/models/purchase_order.py

@ -2,7 +2,7 @@
# Robin Keunen <robin@coopiteasy.be>
# Vincent Van Rossem <vincent@coopiteasy.be>
# 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):

5
purchase_order_generator/models/purchase_order_generator.py

@ -3,7 +3,7 @@
# Vincent Van Rossem <vincent@coopiteasy.be>
# 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
@ -20,8 +20,7 @@ class PurchaseOrderGenerator(models.Model):
"converted into a purchase order.",
)
date_planned = fields.Datetime(
string="Date Planned",
default=fields.Datetime.now,
string="Date Planned", default=fields.Datetime.now
)
supplier_id = fields.Many2one(
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).
import logging
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
@ -102,7 +102,7 @@ class PurchaseOrderGeneratorLine(models.Model):
if cpol.supplierinfo_id and cpol.supplierinfo_id.product_code:
product_code = cpol.supplierinfo_id.product_code
product_name = cpol.product_template_id.name
cpol_name = "[%s] %s" % (product_code, product_name)
cpol_name = "[{}] {}".format(product_code, product_name)
else:
cpol_name = cpol.product_template_id.name
cpol.name = cpol_name

2
purchase_order_generator/tests/test_pog.py

@ -2,7 +2,7 @@
# @author: Robin Keunen <robin@coopiteasy.be>
# 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):

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)
):
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
Loading…
Cancel
Save