bug fix
This commit is contained in:
parent
ff806505e2
commit
26923e20b9
|
|
@ -1 +1 @@
|
||||||
from . import models
|
from . import models
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
{
|
{
|
||||||
'name': 'Consolidated Payslip Grid (OWL)',
|
'name': 'Consolidated Payslip Grid (OWL)',
|
||||||
'version': '1.0',
|
'version': '1.0',
|
||||||
'category': 'Human Resources',
|
'category': 'Human Resources',
|
||||||
'summary': 'Editable Consolidated Payslip Grid (OWL + pqGrid)',
|
'summary': 'Editable Consolidated Payslip Grid (OWL + pqGrid)',
|
||||||
'author': 'Raman Marikanti',
|
'author': 'Raman Marikanti',
|
||||||
'depends': ['hr_payroll', 'web'],
|
'depends': ['hr_payroll', 'web'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'views/batch_payslip_view.xml',
|
'views/batch_payslip_view.xml',
|
||||||
],
|
],
|
||||||
'assets': {
|
'assets': {
|
||||||
'web.assets_backend': [
|
'web.assets_backend': [
|
||||||
# Internal module JS and XML files (ensure correct paths within 'static/src')
|
# Internal module JS and XML files (ensure correct paths within 'static/src')
|
||||||
'consolidated_batch_payslip/static/src/components/pqgrid_batch_payslip/pqgrid_batch_payslip.js',
|
'consolidated_batch_payslip/static/src/components/pqgrid_batch_payslip/pqgrid_batch_payslip.js',
|
||||||
'consolidated_batch_payslip/static/src/components/pqgrid_batch_payslip/pqgrid_batch_payslip.xml',
|
'consolidated_batch_payslip/static/src/components/pqgrid_batch_payslip/pqgrid_batch_payslip.xml',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
'installable': True,
|
'installable': True,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,312 +1,312 @@
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class HrPayslipRun(models.Model):
|
class HrPayslipRun(models.Model):
|
||||||
_inherit = 'hr.payslip.run'
|
_inherit = 'hr.payslip.run'
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_consolidated_attendance_data(self, payslip_run_id):
|
def get_consolidated_attendance_data(self, payslip_run_id):
|
||||||
"""
|
"""
|
||||||
Returns consolidated attendance and leave data for all employees in the payslip run
|
Returns consolidated attendance and leave data for all employees in the payslip run
|
||||||
"""
|
"""
|
||||||
# Get all payslips in this batch
|
# Get all payslips in this batch
|
||||||
payslips = self.env['hr.payslip'].search([('payslip_run_id', '=', payslip_run_id)])
|
payslips = self.env['hr.payslip'].search([('payslip_run_id', '=', payslip_run_id)])
|
||||||
|
|
||||||
if not payslips:
|
if not payslips:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for slip in payslips:
|
for slip in payslips:
|
||||||
employee = slip.employee_id
|
employee = slip.employee_id
|
||||||
contract = slip.contract_id
|
contract = slip.contract_id
|
||||||
|
|
||||||
# Get attendance data
|
# Get attendance data
|
||||||
attendance_days = self._get_attendance_days(slip)
|
attendance_days = self._get_attendance_days(slip)
|
||||||
worked_days = self._get_worked_days(slip)
|
worked_days = self._get_worked_days(slip)
|
||||||
leave_days = self._get_leave_days(slip)
|
leave_days = self._get_leave_days(slip)
|
||||||
lop_days = self._get_lop_days(slip)
|
lop_days = self._get_lop_days(slip)
|
||||||
|
|
||||||
# Get leave balances
|
# Get leave balances
|
||||||
leave_balances = self._get_leave_balances(employee,slip.date_from,slip.date_to)
|
leave_balances = self._get_leave_balances(employee,slip.date_from,slip.date_to)
|
||||||
leave_taken = self._get_leave_taken(slip)
|
leave_taken = self._get_leave_taken(slip)
|
||||||
|
|
||||||
result.append({
|
result.append({
|
||||||
'id': slip.id,
|
'id': slip.id,
|
||||||
'employee_id': (employee.id, employee.name),
|
'employee_id': (employee.id, employee.name),
|
||||||
'employee_code': employee.employee_id or '',
|
'employee_code': employee.employee_id or '',
|
||||||
'department_id': (employee.department_id.id,
|
'department_id': (employee.department_id.id,
|
||||||
employee.department_id.name) if employee.department_id else False,
|
employee.department_id.name) if employee.department_id else False,
|
||||||
'total_days': (slip.date_to - slip.date_from).days + 1,
|
'total_days': (slip.date_to - slip.date_from).days + 1,
|
||||||
'worked_days': worked_days,
|
'worked_days': worked_days,
|
||||||
'attendance_days': attendance_days,
|
'attendance_days': attendance_days,
|
||||||
'leave_days': leave_days,
|
'leave_days': leave_days,
|
||||||
'lop_days': lop_days,
|
'lop_days': lop_days,
|
||||||
'doj':contract.date_start,
|
'doj':contract.date_start,
|
||||||
'birthday':employee.birthday,
|
'birthday':employee.birthday,
|
||||||
'bank': employee.bank_account_id.display_name if employee.bank_account_id else '-',
|
'bank': employee.bank_account_id.display_name if employee.bank_account_id else '-',
|
||||||
'sick_leave_balance': leave_balances.get('LEAVE110', 0),
|
'sick_leave_balance': leave_balances.get('LEAVE110', 0),
|
||||||
'casual_leave_balance': leave_balances.get('LEAVE120', 0),
|
'casual_leave_balance': leave_balances.get('LEAVE120', 0),
|
||||||
'privilege_leave_balance': leave_balances.get('LEAVE100', 0),
|
'privilege_leave_balance': leave_balances.get('LEAVE100', 0),
|
||||||
'sick_leave_taken': leave_taken.get('sick', 0),
|
'sick_leave_taken': leave_taken.get('sick', 0),
|
||||||
'casual_leave_taken': leave_taken.get('casual', 0),
|
'casual_leave_taken': leave_taken.get('casual', 0),
|
||||||
'privilege_leave_taken': leave_taken.get('privilege', 0),
|
'privilege_leave_taken': leave_taken.get('privilege', 0),
|
||||||
'state': slip.state,
|
'state': slip.state,
|
||||||
'lines':self.get_payslip_lines_data(slip),
|
'lines':self.get_payslip_lines_data(slip),
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def sub_columns(self,payslip_run_id):
|
def sub_columns(self,payslip_run_id):
|
||||||
payslips = self.env['hr.payslip'].search([('payslip_run_id', '=', payslip_run_id)])
|
payslips = self.env['hr.payslip'].search([('payslip_run_id', '=', payslip_run_id)])
|
||||||
|
|
||||||
names = payslips.line_ids.filtered(lambda x:x.amount != 0)
|
names = payslips.line_ids.filtered(lambda x:x.amount != 0)
|
||||||
code_name_dict = {line.code+line.name.replace(" ", "_")if line.code in ['REIMBURSEMENT','DEDUCTION'] else line.code : line.name for line in names}
|
code_name_dict = {line.code+line.name.replace(" ", "_")if line.code in ['REIMBURSEMENT','DEDUCTION'] else line.code : line.name for line in names}
|
||||||
|
|
||||||
columns = []
|
columns = []
|
||||||
for code, name in code_name_dict.items():
|
for code, name in code_name_dict.items():
|
||||||
columns.append({
|
columns.append({
|
||||||
'title': name,
|
'title': name,
|
||||||
'dataIndx': code,
|
'dataIndx': code,
|
||||||
'width': 150,
|
'width': 150,
|
||||||
'editable': False,
|
'editable': False,
|
||||||
'summary': {'type': "sum_"},
|
'summary': {'type': "sum_"},
|
||||||
})
|
})
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
def save_consolidated_attendance_data(self, payslip_run_id, data):
|
def save_consolidated_attendance_data(self, payslip_run_id, data):
|
||||||
"""
|
"""
|
||||||
Saves the edited attendance and leave data from the grid
|
Saves the edited attendance and leave data from the grid
|
||||||
"""
|
"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|
||||||
for item in data:
|
for item in data:
|
||||||
slip = self.env['hr.payslip'].browse(item['id'])
|
slip = self.env['hr.payslip'].browse(item['id'])
|
||||||
if slip.state != 'draft':
|
if slip.state != 'draft':
|
||||||
raise UserError(_("Cannot edit payslip in %s state") % slip.state)
|
raise UserError(_("Cannot edit payslip in %s state") % slip.state)
|
||||||
|
|
||||||
# Update LOP days
|
# Update LOP days
|
||||||
if 'lop_days' in item:
|
if 'lop_days' in item:
|
||||||
self._update_lop_days(slip, float(item['lop_days']))
|
self._update_lop_days(slip, float(item['lop_days']))
|
||||||
|
|
||||||
# Update leave days taken
|
# Update leave days taken
|
||||||
leave_updates = {}
|
leave_updates = {}
|
||||||
if 'sick_leave_taken' in item:
|
if 'sick_leave_taken' in item:
|
||||||
leave_updates['sick'] = float(item['sick_leave_taken'])
|
leave_updates['sick'] = float(item['sick_leave_taken'])
|
||||||
if 'casual_leave_taken' in item:
|
if 'casual_leave_taken' in item:
|
||||||
leave_updates['casual'] = float(item['casual_leave_taken'])
|
leave_updates['casual'] = float(item['casual_leave_taken'])
|
||||||
if 'privilege_leave_taken' in item:
|
if 'privilege_leave_taken' in item:
|
||||||
leave_updates['privilege'] = float(item['privilege_leave_taken'])
|
leave_updates['privilege'] = float(item['privilege_leave_taken'])
|
||||||
|
|
||||||
if leave_updates:
|
if leave_updates:
|
||||||
self._update_leave_taken(slip, leave_updates)
|
self._update_leave_taken(slip, leave_updates)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def recalculate_lop_days(self, payslip_run_id):
|
def recalculate_lop_days(self, payslip_run_id):
|
||||||
"""
|
"""
|
||||||
Recalculates LOP days for all payslips in the batch based on attendance
|
Recalculates LOP days for all payslips in the batch based on attendance
|
||||||
"""
|
"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
payslips = self.env['hr.payslip'].search([
|
payslips = self.env['hr.payslip'].search([
|
||||||
('payslip_run_id', '=', payslip_run_id),
|
('payslip_run_id', '=', payslip_run_id),
|
||||||
('state', '=', 'draft')
|
('state', '=', 'draft')
|
||||||
])
|
])
|
||||||
|
|
||||||
for slip in payslips:
|
for slip in payslips:
|
||||||
attendance_days = self._get_attendance_days(slip)
|
attendance_days = self._get_attendance_days(slip)
|
||||||
expected_days = (slip.date_to - slip.date_from).days + 1
|
expected_days = (slip.date_to - slip.date_from).days + 1
|
||||||
lop_days = expected_days - attendance_days
|
lop_days = expected_days - attendance_days
|
||||||
self._update_lop_days(slip, lop_days)
|
self._update_lop_days(slip, lop_days)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def validate_all_attendance_data(self, payslip_run_id):
|
def validate_all_attendance_data(self, payslip_run_id):
|
||||||
"""
|
"""
|
||||||
Marks all payslips in the batch as validated
|
Marks all payslips in the batch as validated
|
||||||
"""
|
"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
payslips = self.env['hr.payslip'].search([
|
payslips = self.env['hr.payslip'].search([
|
||||||
('payslip_run_id', '=', payslip_run_id),
|
('payslip_run_id', '=', payslip_run_id),
|
||||||
('state', '=', 'draft')
|
('state', '=', 'draft')
|
||||||
])
|
])
|
||||||
|
|
||||||
if not payslips:
|
if not payslips:
|
||||||
raise UserError(_("No draft payslips found in this batch"))
|
raise UserError(_("No draft payslips found in this batch"))
|
||||||
|
|
||||||
payslips.write({'state': 'verify'})
|
payslips.write({'state': 'verify'})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Helper methods
|
# Helper methods
|
||||||
def _get_attendance_days(self, payslip):
|
def _get_attendance_days(self, payslip):
|
||||||
"""
|
"""
|
||||||
Returns number of days employee was present (based on attendance records)
|
Returns number of days employee was present (based on attendance records)
|
||||||
"""
|
"""
|
||||||
attendance_records = self.env['hr.attendance'].search([
|
attendance_records = self.env['hr.attendance'].search([
|
||||||
('employee_id', '=', payslip.employee_id.id),
|
('employee_id', '=', payslip.employee_id.id),
|
||||||
('check_in', '>=', payslip.date_from),
|
('check_in', '>=', payslip.date_from),
|
||||||
('check_in', '<=', payslip.date_to)
|
('check_in', '<=', payslip.date_to)
|
||||||
])
|
])
|
||||||
|
|
||||||
# Group by day
|
# Group by day
|
||||||
unique_days = set()
|
unique_days = set()
|
||||||
for att in attendance_records:
|
for att in attendance_records:
|
||||||
unique_days.add(att.check_in.date())
|
unique_days.add(att.check_in.date())
|
||||||
|
|
||||||
return len(unique_days)
|
return len(unique_days)
|
||||||
|
|
||||||
def _get_worked_days(self, payslip):
|
def _get_worked_days(self, payslip):
|
||||||
"""
|
"""
|
||||||
Returns number of working days (excluding weekends and holidays)
|
Returns number of working days (excluding weekends and holidays)
|
||||||
"""
|
"""
|
||||||
return payslip._get_worked_days_line_number_of_days('WORK100') # Assuming WORK100 is your work code
|
return payslip._get_worked_days_line_number_of_days('WORK100') # Assuming WORK100 is your work code
|
||||||
|
|
||||||
def get_payslip_lines_data(self, payslip_id):
|
def get_payslip_lines_data(self, payslip_id):
|
||||||
list = []
|
list = []
|
||||||
for line in payslip_id.line_ids:
|
for line in payslip_id.line_ids:
|
||||||
list.append({
|
list.append({
|
||||||
'name': line.name,
|
'name': line.name,
|
||||||
'code': line.code + line.name.replace(" ", "_")if line.code in ['REIMBURSEMENT','DEDUCTION'] else line.code,
|
'code': line.code + line.name.replace(" ", "_")if line.code in ['REIMBURSEMENT','DEDUCTION'] else line.code,
|
||||||
'category_id': line.category_id.name if line.category_id else False,
|
'category_id': line.category_id.name if line.category_id else False,
|
||||||
'amount': line.amount,
|
'amount': line.amount,
|
||||||
'quantity': line.quantity,
|
'quantity': line.quantity,
|
||||||
'rate': line.rate
|
'rate': line.rate
|
||||||
})
|
})
|
||||||
return list
|
return list
|
||||||
|
|
||||||
def _get_leave_days(self, payslip):
|
def _get_leave_days(self, payslip):
|
||||||
"""
|
"""
|
||||||
Returns total leave days taken in this period
|
Returns total leave days taken in this period
|
||||||
"""
|
"""
|
||||||
leave_lines = payslip.worked_days_line_ids.filtered(
|
leave_lines = payslip.worked_days_line_ids.filtered(
|
||||||
lambda l: l.code in ['LEAVE110', 'LEAVE90', 'LEAVE100', 'LEAVE120'] # Your leave codes
|
lambda l: l.code in ['LEAVE110', 'LEAVE90', 'LEAVE100', 'LEAVE120'] # Your leave codes
|
||||||
)
|
)
|
||||||
return sum(leave_lines.mapped('number_of_days'))
|
return sum(leave_lines.mapped('number_of_days'))
|
||||||
|
|
||||||
def _get_lop_days(self, payslip):
|
def _get_lop_days(self, payslip):
|
||||||
"""
|
"""
|
||||||
Returns LOP days from payslip
|
Returns LOP days from payslip
|
||||||
"""
|
"""
|
||||||
lop_line = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'LEAVE90')
|
lop_line = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'LEAVE90')
|
||||||
return lop_line.number_of_days if lop_line else 0
|
return lop_line.number_of_days if lop_line else 0
|
||||||
|
|
||||||
|
|
||||||
def _get_leave_taken(self, payslip):
|
def _get_leave_taken(self, payslip):
|
||||||
"""
|
"""
|
||||||
Returns leave days taken in this payslip period
|
Returns leave days taken in this payslip period
|
||||||
"""
|
"""
|
||||||
leave_lines = payslip.worked_days_line_ids
|
leave_lines = payslip.worked_days_line_ids
|
||||||
return {
|
return {
|
||||||
'sick': sum(leave_lines.filtered(lambda l: l.code == 'LEAVE110').mapped('number_of_days')),
|
'sick': sum(leave_lines.filtered(lambda l: l.code == 'LEAVE110').mapped('number_of_days')),
|
||||||
'casual': sum(leave_lines.filtered(lambda l: l.code == 'LEAVE120').mapped('number_of_days')),
|
'casual': sum(leave_lines.filtered(lambda l: l.code == 'LEAVE120').mapped('number_of_days')),
|
||||||
'privilege': sum(leave_lines.filtered(lambda l: l.code == 'LEAVE100').mapped('number_of_days')),
|
'privilege': sum(leave_lines.filtered(lambda l: l.code == 'LEAVE100').mapped('number_of_days')),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _update_lop_days(self, payslip, days):
|
def _update_lop_days(self, payslip, days):
|
||||||
"""
|
"""
|
||||||
Updates LOP days in the payslip
|
Updates LOP days in the payslip
|
||||||
"""
|
"""
|
||||||
WorkedDays = self.env['hr.payslip.worked_days']
|
WorkedDays = self.env['hr.payslip.worked_days']
|
||||||
lop_line = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'LOP')
|
lop_line = payslip.worked_days_line_ids.filtered(lambda l: l.code == 'LOP')
|
||||||
|
|
||||||
if lop_line:
|
if lop_line:
|
||||||
if days > 0:
|
if days > 0:
|
||||||
lop_line.write({'number_of_days': days})
|
lop_line.write({'number_of_days': days})
|
||||||
else:
|
else:
|
||||||
lop_line.unlink()
|
lop_line.unlink()
|
||||||
elif days > 0:
|
elif days > 0:
|
||||||
WorkedDays.create({
|
WorkedDays.create({
|
||||||
'payslip_id': payslip.id,
|
'payslip_id': payslip.id,
|
||||||
'name': _('Loss of Pay'),
|
'name': _('Loss of Pay'),
|
||||||
'code': 'LOP',
|
'code': 'LOP',
|
||||||
'number_of_days': days,
|
'number_of_days': days,
|
||||||
'number_of_hours': days * 8, # Assuming 8-hour work day
|
'number_of_hours': days * 8, # Assuming 8-hour work day
|
||||||
'contract_id': payslip.contract_id.id,
|
'contract_id': payslip.contract_id.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
def _get_leave_balances(self, employee, date_from, date_to):
|
def _get_leave_balances(self, employee, date_from, date_to):
|
||||||
Leave = self.env['hr.leave']
|
Leave = self.env['hr.leave']
|
||||||
Allocation = self.env['hr.leave.allocation']
|
Allocation = self.env['hr.leave.allocation']
|
||||||
leave_types = self.env['hr.leave.type'].search([])
|
leave_types = self.env['hr.leave.type'].search([])
|
||||||
|
|
||||||
balances = {}
|
balances = {}
|
||||||
for leave_type in leave_types:
|
for leave_type in leave_types:
|
||||||
# Approved allocations within or before payslip period
|
# Approved allocations within or before payslip period
|
||||||
allocations = Allocation.search([
|
allocations = Allocation.search([
|
||||||
('employee_id', '=', employee.id),
|
('employee_id', '=', employee.id),
|
||||||
('state', '=', 'validate'),
|
('state', '=', 'validate'),
|
||||||
('holiday_status_id', '=', leave_type.id),
|
('holiday_status_id', '=', leave_type.id),
|
||||||
('date_from', '<=', str(date_to)), # Allocation should be active during payslip
|
('date_from', '<=', str(date_to)), # Allocation should be active during payslip
|
||||||
])
|
])
|
||||||
allocated = sum(alloc.number_of_days for alloc in allocations)
|
allocated = sum(alloc.number_of_days for alloc in allocations)
|
||||||
|
|
||||||
# Approved leaves within the payslip period
|
# Approved leaves within the payslip period
|
||||||
leaves = Leave.search([
|
leaves = Leave.search([
|
||||||
('employee_id', '=', employee.id),
|
('employee_id', '=', employee.id),
|
||||||
('state', '=', 'validate'),
|
('state', '=', 'validate'),
|
||||||
('holiday_status_id', '=', leave_type.id),
|
('holiday_status_id', '=', leave_type.id),
|
||||||
('request_date_to', '<=', str(date_to))
|
('request_date_to', '<=', str(date_to))
|
||||||
])
|
])
|
||||||
used = sum(leave.number_of_days for leave in leaves)
|
used = sum(leave.number_of_days for leave in leaves)
|
||||||
|
|
||||||
# Key: leave code or fallback to name
|
# Key: leave code or fallback to name
|
||||||
code = leave_type.work_entry_type_id.code
|
code = leave_type.work_entry_type_id.code
|
||||||
balances[code] = allocated - used
|
balances[code] = allocated - used
|
||||||
|
|
||||||
return balances
|
return balances
|
||||||
|
|
||||||
def _update_leave_taken(self, payslip, leave_data):
|
def _update_leave_taken(self, payslip, leave_data):
|
||||||
"""
|
"""
|
||||||
Updates leave days taken in the payslip
|
Updates leave days taken in the payslip
|
||||||
"""
|
"""
|
||||||
WorkedDays = self.env['hr.payslip.worked_days']
|
WorkedDays = self.env['hr.payslip.worked_days']
|
||||||
|
|
||||||
for leave_type, days in leave_data.items():
|
for leave_type, days in leave_data.items():
|
||||||
code = leave_type.upper()
|
code = leave_type.upper()
|
||||||
line = payslip.worked_days_line_ids.filtered(lambda l: l.code == code)
|
line = payslip.worked_days_line_ids.filtered(lambda l: l.code == code)
|
||||||
|
|
||||||
|
|
||||||
if line:
|
if line:
|
||||||
if days > 0:
|
if days > 0:
|
||||||
line.write({'number_of_days': days})
|
line.write({'number_of_days': days})
|
||||||
else:
|
else:
|
||||||
line.unlink()
|
line.unlink()
|
||||||
elif days > 0:
|
elif days > 0:
|
||||||
WorkedDays.create({
|
WorkedDays.create({
|
||||||
'payslip_id': payslip.id,
|
'payslip_id': payslip.id,
|
||||||
'name': _(leave_type.capitalize() + ' Leave'),
|
'name': _(leave_type.capitalize() + ' Leave'),
|
||||||
'code': code,
|
'code': code,
|
||||||
'number_of_days': days,
|
'number_of_days': days,
|
||||||
'number_of_hours': days * 8, # Assuming 8-hour work day
|
'number_of_hours': days * 8, # Assuming 8-hour work day
|
||||||
'contract_id': payslip.contract_id.id,
|
'contract_id': payslip.contract_id.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class HrPayslip(models.Model):
|
class HrPayslip(models.Model):
|
||||||
_inherit = 'hr.payslip'
|
_inherit = 'hr.payslip'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_payslip_lines_data(self, payslip_id):
|
def get_payslip_lines_data(self, payslip_id):
|
||||||
payslip = self.browse(payslip_id)
|
payslip = self.browse(payslip_id)
|
||||||
return [{
|
return [{
|
||||||
'name': line.name,
|
'name': line.name,
|
||||||
'code': line.code,
|
'code': line.code,
|
||||||
'category_id': (line.category_id.id, line.category_id.name),
|
'category_id': (line.category_id.id, line.category_id.name),
|
||||||
'amount': line.amount,
|
'amount': line.amount,
|
||||||
'quantity': line.quantity,
|
'quantity': line.quantity,
|
||||||
'rate': line.rate
|
'rate': line.rate
|
||||||
} for line in payslip.line_ids]
|
} for line in payslip.line_ids]
|
||||||
|
|
||||||
def action_open_payslips(self):
|
def action_open_payslips(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
action = self.env["ir.actions.actions"]._for_xml_id("hr_payroll.action_view_hr_payslip_month_form")
|
action = self.env["ir.actions.actions"]._for_xml_id("hr_payroll.action_view_hr_payslip_month_form")
|
||||||
action['views'] = [[False, "form"]]
|
action['views'] = [[False, "form"]]
|
||||||
action['res_id'] = self.id
|
action['res_id'] = self.id
|
||||||
action['target'] = 'new'
|
action['target'] = 'new'
|
||||||
return action
|
return action
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
id,name,model_id:id,group_id,perm_read,perm_write,perm_create,perm_unlink
|
id,name,model_id:id,group_id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<templates xml:space="preserve">
|
<templates xml:space="preserve">
|
||||||
<t t-name="ConsolidatedPayslipGrid" owl="1">
|
<t t-name="ConsolidatedPayslipGrid" owl="1">
|
||||||
<div t-ref="gridContainer" style="width: 100%; height: 600px;"></div>
|
<div t-ref="gridContainer" style="width: 100%; height: 600px;"></div>
|
||||||
</t>
|
</t>
|
||||||
</templates>
|
</templates>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="view_hr_payslip_run_form_inherit" model="ir.ui.view">
|
<record id="view_hr_payslip_run_form_inherit" model="ir.ui.view">
|
||||||
<field name="name">hr.payslip.run.form.inherit.consolidated.pqgrid.owl</field>
|
<field name="name">hr.payslip.run.form.inherit.consolidated.pqgrid.owl</field>
|
||||||
<field name="model">hr.payslip.run</field>
|
<field name="model">hr.payslip.run</field>
|
||||||
<field name="inherit_id" ref="hr_payroll.hr_payslip_run_form"/>
|
<field name="inherit_id" ref="hr_payroll.hr_payslip_run_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//sheet" position="inside">
|
<xpath expr="//sheet" position="inside">
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Consolidated Payslip">
|
<page string="Consolidated Payslip">
|
||||||
<div class="o_consolidated_grid" style="height:600px; margin-top:10px;">
|
<div class="o_consolidated_grid" style="height:600px; margin-top:10px;">
|
||||||
<widget name="ConsolidatedPayslipGrid" batchId="context.active_id"/>
|
<widget name="ConsolidatedPayslipGrid" batchId="context.active_id"/>
|
||||||
</div>
|
</div>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
{
|
{
|
||||||
'name': 'CWF Timesheet Update',
|
'name': 'CWF Timesheet Update',
|
||||||
'version': '1.0',
|
'version': '1.0',
|
||||||
'category': 'Human Resources',
|
'category': 'Human Resources',
|
||||||
'summary': 'Manage and update weekly timesheets for CWF department',
|
'summary': 'Manage and update weekly timesheets for CWF department',
|
||||||
'author': 'Your Name or Company',
|
'author': 'Your Name or Company',
|
||||||
'depends': ['hr_attendance_extended','web', 'mail', 'base','hr_emp_dashboard','hr_employee_extended'],
|
'depends': ['hr_attendance_extended','web', 'mail', 'base','hr_emp_dashboard','hr_employee_extended'],
|
||||||
'data': [
|
'data': [
|
||||||
# 'views/timesheet_form.xml',
|
# 'views/timesheet_form.xml',
|
||||||
'security/security.xml',
|
'security/security.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'views/timesheet_view.xml',
|
'views/timesheet_view.xml',
|
||||||
'views/timesheet_weekly_view.xml',
|
'views/timesheet_weekly_view.xml',
|
||||||
'data/email_template.xml',
|
'data/email_template.xml',
|
||||||
],
|
],
|
||||||
'assets': {
|
'assets': {
|
||||||
'web.assets_backend': [
|
'web.assets_backend': [
|
||||||
'cwf_timesheet/static/src/js/timesheet_form.js',
|
'cwf_timesheet/static/src/js/timesheet_form.js',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'application': True,
|
'application': True,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
from odoo import http
|
from odoo import http
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
|
|
||||||
class TimesheetController(http.Controller):
|
class TimesheetController(http.Controller):
|
||||||
|
|
||||||
@http.route('/timesheet/form', auth='user', website=True)
|
@http.route('/timesheet/form', auth='user', website=True)
|
||||||
def timesheet_form(self, **kw):
|
def timesheet_form(self, **kw):
|
||||||
# This will render the template for the timesheet form
|
# This will render the template for the timesheet form
|
||||||
return request.render('timesheet_form', {})
|
return request.render('timesheet_form', {})
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,45 @@
|
||||||
<odoo>
|
<odoo>
|
||||||
<data noupdate="0">
|
<data noupdate="0">
|
||||||
<record id="email_template_timesheet_weekly_update" model="mail.template">
|
<record id="email_template_timesheet_weekly_update" model="mail.template">
|
||||||
<field name="name">Timesheet Update Reminder</field>
|
<field name="name">Timesheet Update Reminder</field>
|
||||||
<field name="model_id" ref="cwf_timesheet.model_cwf_weekly_timesheet"/>
|
<field name="model_id" ref="cwf_timesheet.model_cwf_weekly_timesheet"/>
|
||||||
<field name="email_from">{{ user.email_formatted }}</field>
|
<field name="email_from">{{ user.email_formatted }}</field>
|
||||||
<field name="email_to">{{ object.employee_id.user_id.email }}</field>
|
<field name="email_to">{{ object.employee_id.user_id.email }}</field>
|
||||||
<field name="subject">Reminder: Update Your Weekly Timesheet</field>
|
<field name="subject">Reminder: Update Your Weekly Timesheet</field>
|
||||||
<field name="description">
|
<field name="description">
|
||||||
Reminder to employee to update their weekly timesheet.
|
Reminder to employee to update their weekly timesheet.
|
||||||
</field>
|
</field>
|
||||||
<field name="body_html" type="html">
|
<field name="body_html" type="html">
|
||||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||||
Dear <t t-esc="ctx['employee_name']">Employee</t>,
|
Dear <t t-esc="ctx['employee_name']">Employee</t>,
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
I hope this message finds you in good spirits. I would like to remind you to please update your weekly timesheet for the period from
|
I hope this message finds you in good spirits. I would like to remind you to please update your weekly timesheet for the period from
|
||||||
<strong>
|
<strong>
|
||||||
<t t-esc="ctx['week_from']"/>
|
<t t-esc="ctx['week_from']"/>
|
||||||
</strong>
|
</strong>
|
||||||
to
|
to
|
||||||
<strong>
|
<strong>
|
||||||
<t t-esc="ctx['week_to']"/>
|
<t t-esc="ctx['week_to']"/>
|
||||||
</strong>.
|
</strong>.
|
||||||
Timely updates are crucial for maintaining accurate records and ensuring smooth processing.
|
Timely updates are crucial for maintaining accurate records and ensuring smooth processing.
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
To make things easier, you can use the link below to update your timesheet:
|
To make things easier, you can use the link below to update your timesheet:
|
||||||
<br/>
|
<br/>
|
||||||
<a href="https://ftprotech.in/odoo/action-261" class="cta-button" target="_blank">Update Timesheet</a>
|
<a href="https://ftprotech.in/odoo/action-261" class="cta-button" target="_blank">Update Timesheet</a>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
Thank you for your attention.
|
Thank you for your attention.
|
||||||
<br/>
|
<br/>
|
||||||
Best regards,
|
Best regards,
|
||||||
<br/>
|
<br/>
|
||||||
<strong>Fast Track Project Pvt Ltd.</strong>
|
<strong>Fast Track Project Pvt Ltd.</strong>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<a href="https://ftprotech.in/" target="_blank">Visit our site</a> for more information.
|
<a href="https://ftprotech.in/" target="_blank">Visit our site</a> for more information.
|
||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,399 +1,399 @@
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api
|
||||||
from odoo.exceptions import ValidationError, UserError
|
from odoo.exceptions import ValidationError, UserError
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
from odoo import _
|
from odoo import _
|
||||||
from calendar import month_name, month
|
from calendar import month_name, month
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
class CwfTimesheetYearly(models.Model):
|
class CwfTimesheetYearly(models.Model):
|
||||||
_name = 'cwf.timesheet.calendar'
|
_name = 'cwf.timesheet.calendar'
|
||||||
_description = "CWF Timesheet Calendar"
|
_description = "CWF Timesheet Calendar"
|
||||||
_rec_name = 'name'
|
_rec_name = 'name'
|
||||||
|
|
||||||
name = fields.Char(string='Year Name', required=True)
|
name = fields.Char(string='Year Name', required=True)
|
||||||
week_period = fields.One2many('cwf.timesheet','cwf_calendar_id')
|
week_period = fields.One2many('cwf.timesheet','cwf_calendar_id')
|
||||||
|
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
('unique_year', 'unique(name)', 'The year must be unique!')
|
('unique_year', 'unique(name)', 'The year must be unique!')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@api.constrains('name')
|
@api.constrains('name')
|
||||||
def _check_year_format(self):
|
def _check_year_format(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
if not record.name.isdigit() or len(record.name) != 4:
|
if not record.name.isdigit() or len(record.name) != 4:
|
||||||
raise ValidationError("Year Name must be a 4-digit number.")
|
raise ValidationError("Year Name must be a 4-digit number.")
|
||||||
|
|
||||||
def generate_week_period(self):
|
def generate_week_period(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
record.week_period.unlink()
|
record.week_period.unlink()
|
||||||
year = int(record.name)
|
year = int(record.name)
|
||||||
|
|
||||||
# Find the first Monday of the year
|
# Find the first Monday of the year
|
||||||
start_date = datetime(year, 1, 1)
|
start_date = datetime(year, 1, 1)
|
||||||
while start_date.weekday() != 0: # Monday is 0 in weekday()
|
while start_date.weekday() != 0: # Monday is 0 in weekday()
|
||||||
start_date += timedelta(days=1)
|
start_date += timedelta(days=1)
|
||||||
|
|
||||||
# Generate weeks from Monday to Sunday
|
# Generate weeks from Monday to Sunday
|
||||||
while start_date.year == year or (start_date - timedelta(days=1)).year == year:
|
while start_date.year == year or (start_date - timedelta(days=1)).year == year:
|
||||||
end_date = start_date + timedelta(days=6)
|
end_date = start_date + timedelta(days=6)
|
||||||
|
|
||||||
self.env['cwf.timesheet'].create({
|
self.env['cwf.timesheet'].create({
|
||||||
'name': f'Week {start_date.strftime("%W")}, {year}',
|
'name': f'Week {start_date.strftime("%W")}, {year}',
|
||||||
'week_start_date': start_date.date(),
|
'week_start_date': start_date.date(),
|
||||||
'week_end_date': end_date.date(),
|
'week_end_date': end_date.date(),
|
||||||
'cwf_calendar_id': record.id,
|
'cwf_calendar_id': record.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
start_date += timedelta(days=7)
|
start_date += timedelta(days=7)
|
||||||
|
|
||||||
def action_generate_weeks(self):
|
def action_generate_weeks(self):
|
||||||
self.generate_week_period()
|
self.generate_week_period()
|
||||||
return {
|
return {
|
||||||
'type': 'ir.actions.client',
|
'type': 'ir.actions.client',
|
||||||
'tag': 'reload',
|
'tag': 'reload',
|
||||||
}
|
}
|
||||||
|
|
||||||
class CwfTimesheet(models.Model):
|
class CwfTimesheet(models.Model):
|
||||||
_name = 'cwf.timesheet'
|
_name = 'cwf.timesheet'
|
||||||
_description = 'CWF Weekly Timesheet'
|
_description = 'CWF Weekly Timesheet'
|
||||||
_rec_name = 'name'
|
_rec_name = 'name'
|
||||||
|
|
||||||
name = fields.Char(string='Week Name', required=True)
|
name = fields.Char(string='Week Name', required=True)
|
||||||
week_start_date = fields.Date(string='Week Start Date', required=True)
|
week_start_date = fields.Date(string='Week Start Date', required=True)
|
||||||
week_end_date = fields.Date(string='Week End Date', required=True)
|
week_end_date = fields.Date(string='Week End Date', required=True)
|
||||||
status = fields.Selection([
|
status = fields.Selection([
|
||||||
('draft', 'Draft'),
|
('draft', 'Draft'),
|
||||||
('submitted', 'Submitted')
|
('submitted', 'Submitted')
|
||||||
], default='draft', string='Status')
|
], default='draft', string='Status')
|
||||||
lines = fields.One2many('cwf.timesheet.line','week_id')
|
lines = fields.One2many('cwf.timesheet.line','week_id')
|
||||||
cwf_calendar_id = fields.Many2one('cwf.timesheet.calendar')
|
cwf_calendar_id = fields.Many2one('cwf.timesheet.calendar')
|
||||||
start_month = fields.Selection(
|
start_month = fields.Selection(
|
||||||
[(str(i), month_name[i]) for i in range(1, 13)],
|
[(str(i), month_name[i]) for i in range(1, 13)],
|
||||||
string='Start Month',
|
string='Start Month',
|
||||||
compute='_compute_months',
|
compute='_compute_months',
|
||||||
store=True
|
store=True
|
||||||
)
|
)
|
||||||
|
|
||||||
end_month = fields.Selection(
|
end_month = fields.Selection(
|
||||||
[(str(i), month_name[i]) for i in range(1, 13)],
|
[(str(i), month_name[i]) for i in range(1, 13)],
|
||||||
string='End Month',
|
string='End Month',
|
||||||
compute='_compute_months',
|
compute='_compute_months',
|
||||||
store=True
|
store=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends('week_start_date', 'week_end_date')
|
@api.depends('week_start_date', 'week_end_date')
|
||||||
def _compute_months(self):
|
def _compute_months(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.week_start_date:
|
if rec.week_start_date:
|
||||||
rec.start_month = str(rec.week_start_date.month)
|
rec.start_month = str(rec.week_start_date.month)
|
||||||
else:
|
else:
|
||||||
rec.start_month = False
|
rec.start_month = False
|
||||||
|
|
||||||
if rec.week_end_date:
|
if rec.week_end_date:
|
||||||
rec.end_month = str(rec.week_end_date.month)
|
rec.end_month = str(rec.week_end_date.month)
|
||||||
else:
|
else:
|
||||||
rec.end_month = False
|
rec.end_month = False
|
||||||
|
|
||||||
@api.depends('name','week_start_date','week_end_date')
|
@api.depends('name','week_start_date','week_end_date')
|
||||||
def _compute_display_name(self):
|
def _compute_display_name(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
rec.display_name = rec.name if not rec.week_start_date and rec.week_end_date else "%s (%s - %s)"%(rec.name,rec.week_start_date.strftime('%-d %b'), rec.week_end_date.strftime('%-d %b') )
|
rec.display_name = rec.name if not rec.week_start_date and rec.week_end_date else "%s (%s - %s)"%(rec.name,rec.week_start_date.strftime('%-d %b'), rec.week_end_date.strftime('%-d %b') )
|
||||||
|
|
||||||
|
|
||||||
def send_timesheet_update_email(self):
|
def send_timesheet_update_email(self):
|
||||||
template = self.env.ref('cwf_timesheet.email_template_timesheet_weekly_update')
|
template = self.env.ref('cwf_timesheet.email_template_timesheet_weekly_update')
|
||||||
# Ensure that we have a valid employee email
|
# Ensure that we have a valid employee email
|
||||||
current_date = fields.Date.from_string(self.week_start_date)
|
current_date = fields.Date.from_string(self.week_start_date)
|
||||||
end_date = fields.Date.from_string(self.week_end_date)
|
end_date = fields.Date.from_string(self.week_end_date)
|
||||||
|
|
||||||
if current_date > end_date:
|
if current_date > end_date:
|
||||||
raise UserError('The start date cannot be after the end date.')
|
raise UserError('The start date cannot be after the end date.')
|
||||||
|
|
||||||
# Get all employees in the department
|
# Get all employees in the department
|
||||||
external_group_id = self.env.ref("hr_employee_extended.group_external_user")
|
external_group_id = self.env.ref("hr_employee_extended.group_external_user")
|
||||||
users = self.env["res.users"].search([("groups_id", "=", external_group_id.id)])
|
users = self.env["res.users"].search([("groups_id", "=", external_group_id.id)])
|
||||||
employees = self.env['hr.employee'].search([('user_id', 'in', users.ids),'|',('doj','=',False),('doj','>=', self.week_start_date)])
|
employees = self.env['hr.employee'].search([('user_id', 'in', users.ids),'|',('doj','=',False),('doj','>=', self.week_start_date)])
|
||||||
print(employees)
|
print(employees)
|
||||||
# Loop through each day of the week and create timesheet lines for each employee
|
# Loop through each day of the week and create timesheet lines for each employee
|
||||||
while current_date <= end_date:
|
while current_date <= end_date:
|
||||||
for employee in employees:
|
for employee in employees:
|
||||||
existing_record = self.env['cwf.weekly.timesheet'].sudo().search([
|
existing_record = self.env['cwf.weekly.timesheet'].sudo().search([
|
||||||
('week_id', '=', self.id),
|
('week_id', '=', self.id),
|
||||||
('employee_id', '=', employee.id)
|
('employee_id', '=', employee.id)
|
||||||
], limit=1)
|
], limit=1)
|
||||||
if not existing_record:
|
if not existing_record:
|
||||||
self.env['cwf.timesheet.line'].sudo().create({
|
self.env['cwf.timesheet.line'].sudo().create({
|
||||||
'week_id': self.id,
|
'week_id': self.id,
|
||||||
'employee_id': employee.id,
|
'employee_id': employee.id,
|
||||||
'week_day':current_date,
|
'week_day':current_date,
|
||||||
})
|
})
|
||||||
current_date += timedelta(days=1)
|
current_date += timedelta(days=1)
|
||||||
self.status = 'submitted'
|
self.status = 'submitted'
|
||||||
for employee in employees:
|
for employee in employees:
|
||||||
weekly_timesheet_exists = self.env['cwf.weekly.timesheet'].sudo().search([('week_id','=',self.id),('employee_id','=',employee.id)])
|
weekly_timesheet_exists = self.env['cwf.weekly.timesheet'].sudo().search([('week_id','=',self.id),('employee_id','=',employee.id)])
|
||||||
|
|
||||||
if not weekly_timesheet_exists:
|
if not weekly_timesheet_exists:
|
||||||
weekly_timesheet = self.env['cwf.weekly.timesheet'].sudo().create({
|
weekly_timesheet = self.env['cwf.weekly.timesheet'].sudo().create({
|
||||||
'week_id': self.id,
|
'week_id': self.id,
|
||||||
'employee_id': employee.id,
|
'employee_id': employee.id,
|
||||||
'status': 'draft'
|
'status': 'draft'
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
weekly_timesheet = weekly_timesheet_exists
|
weekly_timesheet = weekly_timesheet_exists
|
||||||
|
|
||||||
# Generate the URL for the newly created weekly_timesheet
|
# Generate the URL for the newly created weekly_timesheet
|
||||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||||
record_url = f"{base_url}/web#id={weekly_timesheet.id}&view_type=form&model=cwf.weekly.timesheet"
|
record_url = f"{base_url}/web#id={weekly_timesheet.id}&view_type=form&model=cwf.weekly.timesheet"
|
||||||
|
|
||||||
weekly_timesheet.update_attendance()
|
weekly_timesheet.update_attendance()
|
||||||
if employee.work_email and weekly_timesheet:
|
if employee.work_email and weekly_timesheet:
|
||||||
email_values = {
|
email_values = {
|
||||||
'email_to': employee.work_email, # Email body from template
|
'email_to': employee.work_email, # Email body from template
|
||||||
'subject': 'Timesheet Update Notification',
|
'subject': 'Timesheet Update Notification',
|
||||||
}
|
}
|
||||||
body_html = template.body_html.replace(
|
body_html = template.body_html.replace(
|
||||||
'https://ftprotech.in/odoo/action-261',
|
'https://ftprotech.in/odoo/action-261',
|
||||||
record_url
|
record_url
|
||||||
),
|
),
|
||||||
render_ctx = {'employee_name':weekly_timesheet.employee_id.name,'week_from':weekly_timesheet.week_id.week_start_date,'week_to':weekly_timesheet.week_id.week_end_date}
|
render_ctx = {'employee_name':weekly_timesheet.employee_id.name,'week_from':weekly_timesheet.week_id.week_start_date,'week_to':weekly_timesheet.week_id.week_end_date}
|
||||||
|
|
||||||
|
|
||||||
template.with_context(default_body_html=body_html,**render_ctx).send_mail(weekly_timesheet.id, email_values=email_values, force_send=True)
|
template.with_context(default_body_html=body_html,**render_ctx).send_mail(weekly_timesheet.id, email_values=email_values, force_send=True)
|
||||||
|
|
||||||
|
|
||||||
class CwfWeeklyTimesheet(models.Model):
|
class CwfWeeklyTimesheet(models.Model):
|
||||||
_name = "cwf.weekly.timesheet"
|
_name = "cwf.weekly.timesheet"
|
||||||
_description = "CWF Weekly Timesheet"
|
_description = "CWF Weekly Timesheet"
|
||||||
_rec_name = 'employee_id'
|
_rec_name = 'employee_id'
|
||||||
|
|
||||||
|
|
||||||
def _default_week_id(self):
|
def _default_week_id(self):
|
||||||
current_date = fields.Date.today()
|
current_date = fields.Date.today()
|
||||||
timesheet = self.env['cwf.timesheet'].sudo().search([
|
timesheet = self.env['cwf.timesheet'].sudo().search([
|
||||||
('week_start_date', '<=', current_date),
|
('week_start_date', '<=', current_date),
|
||||||
('week_end_date', '>=', current_date)
|
('week_end_date', '>=', current_date)
|
||||||
], limit=1)
|
], limit=1)
|
||||||
return timesheet.id if timesheet else False
|
return timesheet.id if timesheet else False
|
||||||
|
|
||||||
def _get_week_id_domain(self):
|
def _get_week_id_domain(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
return [('week_start_date.month_number','=',2)]
|
return [('week_start_date.month_number','=',2)]
|
||||||
pass
|
pass
|
||||||
|
|
||||||
month_id = fields.Selection(
|
month_id = fields.Selection(
|
||||||
[(str(i), month_name[i]) for i in range(1, 13)],
|
[(str(i), month_name[i]) for i in range(1, 13)],
|
||||||
string='Month'
|
string='Month'
|
||||||
)
|
)
|
||||||
week_id = fields.Many2one('cwf.timesheet', 'Week', default=lambda self: self._default_week_id())
|
week_id = fields.Many2one('cwf.timesheet', 'Week', default=lambda self: self._default_week_id())
|
||||||
|
|
||||||
employee_id = fields.Many2one('hr.employee', default=lambda self: self.env.user.employee_id.id)
|
employee_id = fields.Many2one('hr.employee', default=lambda self: self.env.user.employee_id.id)
|
||||||
cwf_timesheet_lines = fields.One2many('cwf.timesheet.line' ,'weekly_timesheet')
|
cwf_timesheet_lines = fields.One2many('cwf.timesheet.line' ,'weekly_timesheet')
|
||||||
status = fields.Selection([('draft','Draft'),('submitted','Submitted')], default='draft')
|
status = fields.Selection([('draft','Draft'),('submitted','Submitted')], default='draft')
|
||||||
week_start_date = fields.Date(related='week_id.week_start_date')
|
week_start_date = fields.Date(related='week_id.week_start_date')
|
||||||
week_end_date = fields.Date(related='week_id.week_end_date')
|
week_end_date = fields.Date(related='week_id.week_end_date')
|
||||||
|
|
||||||
@api.onchange('month_id')
|
@api.onchange('month_id')
|
||||||
def _onchange_month(self):
|
def _onchange_month(self):
|
||||||
if self.month_id:
|
if self.month_id:
|
||||||
year = self.week_start_date.year if self.week_start_date else fields.Date.today().year
|
year = self.week_start_date.year if self.week_start_date else fields.Date.today().year
|
||||||
start = date(year, int(self.month_id), 1)
|
start = date(year, int(self.month_id), 1)
|
||||||
if int(self.month_id) == 12:
|
if int(self.month_id) == 12:
|
||||||
end = date(year + 1, 1, 1) - timedelta(days=1)
|
end = date(year + 1, 1, 1) - timedelta(days=1)
|
||||||
else:
|
else:
|
||||||
end = date(year, int(self.month_id) + 1, 1) - timedelta(days=1)
|
end = date(year, int(self.month_id) + 1, 1) - timedelta(days=1)
|
||||||
self = self.with_context(month_start=start, month_end=end)
|
self = self.with_context(month_start=start, month_end=end)
|
||||||
|
|
||||||
@api.onchange('week_id')
|
@api.onchange('week_id')
|
||||||
def _onchange_week_id(self):
|
def _onchange_week_id(self):
|
||||||
if self.week_id and self.week_id.week_start_date:
|
if self.week_id and self.week_id.week_start_date:
|
||||||
self.month_id = str(self.week_id.week_start_date.month)
|
self.month_id = str(self.week_id.week_start_date.month)
|
||||||
|
|
||||||
@api.constrains('week_id', 'employee_id')
|
@api.constrains('week_id', 'employee_id')
|
||||||
def _check_unique_week_employee(self):
|
def _check_unique_week_employee(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
if record.week_id.week_start_date > fields.Date.today():
|
if record.week_id.week_start_date > fields.Date.today():
|
||||||
raise ValidationError(_("You Can't select future week period"))
|
raise ValidationError(_("You Can't select future week period"))
|
||||||
# Search for existing records with the same week_id and employee_id
|
# Search for existing records with the same week_id and employee_id
|
||||||
existing_record = self.env['cwf.weekly.timesheet'].search([
|
existing_record = self.env['cwf.weekly.timesheet'].search([
|
||||||
('week_id', '=', record.week_id.id),
|
('week_id', '=', record.week_id.id),
|
||||||
('employee_id', '=', record.employee_id.id)
|
('employee_id', '=', record.employee_id.id)
|
||||||
], limit=1)
|
], limit=1)
|
||||||
|
|
||||||
# If an existing record is found and it's not the current record (in case of update), raise an error
|
# If an existing record is found and it's not the current record (in case of update), raise an error
|
||||||
if existing_record and existing_record.id != record.id:
|
if existing_record and existing_record.id != record.id:
|
||||||
raise ValidationError("A timesheet for this employee already exists for the selected week.")
|
raise ValidationError("A timesheet for this employee already exists for the selected week.")
|
||||||
|
|
||||||
def update_attendance(self):
|
def update_attendance(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
# Get the week start and end date
|
# Get the week start and end date
|
||||||
week_start_date = rec.week_id.week_start_date
|
week_start_date = rec.week_id.week_start_date
|
||||||
week_end_date = rec.week_id.week_end_date
|
week_end_date = rec.week_id.week_end_date
|
||||||
|
|
||||||
# Convert start and end dates to datetime objects for proper filtering
|
# Convert start and end dates to datetime objects for proper filtering
|
||||||
week_start_datetime = datetime.combine(week_start_date, datetime.min.time())
|
week_start_datetime = datetime.combine(week_start_date, datetime.min.time())
|
||||||
week_end_datetime = datetime.combine(week_end_date, datetime.max.time())
|
week_end_datetime = datetime.combine(week_end_date, datetime.max.time())
|
||||||
|
|
||||||
# Delete timesheet lines that are outside the week range
|
# Delete timesheet lines that are outside the week range
|
||||||
rec.cwf_timesheet_lines.filtered(lambda line:
|
rec.cwf_timesheet_lines.filtered(lambda line:
|
||||||
line.week_day < week_start_date or line.week_day > week_end_date
|
line.week_day < week_start_date or line.week_day > week_end_date
|
||||||
).unlink()
|
).unlink()
|
||||||
|
|
||||||
# Search for attendance records that fall within the week period and match the employee
|
# Search for attendance records that fall within the week period and match the employee
|
||||||
hr_attendance_records = self.env['hr.attendance'].sudo().search([
|
hr_attendance_records = self.env['hr.attendance'].sudo().search([
|
||||||
('check_in', '>=', week_start_datetime),
|
('check_in', '>=', week_start_datetime),
|
||||||
('check_out', '<=', week_end_datetime),
|
('check_out', '<=', week_end_datetime),
|
||||||
('employee_id', '=', rec.employee_id.id)
|
('employee_id', '=', rec.employee_id.id)
|
||||||
])
|
])
|
||||||
|
|
||||||
# Group the attendance records by date
|
# Group the attendance records by date
|
||||||
attendance_by_date = {}
|
attendance_by_date = {}
|
||||||
for attendance in hr_attendance_records:
|
for attendance in hr_attendance_records:
|
||||||
attendance_date = attendance.check_in.date()
|
attendance_date = attendance.check_in.date()
|
||||||
if attendance_date not in attendance_by_date:
|
if attendance_date not in attendance_by_date:
|
||||||
attendance_by_date[attendance_date] = []
|
attendance_by_date[attendance_date] = []
|
||||||
attendance_by_date[attendance_date].append(attendance)
|
attendance_by_date[attendance_date].append(attendance)
|
||||||
|
|
||||||
# Get all the dates within the week period
|
# Get all the dates within the week period
|
||||||
all_week_dates = [week_start_date + timedelta(days=i) for i in
|
all_week_dates = [week_start_date + timedelta(days=i) for i in
|
||||||
range((week_end_date - week_start_date).days + 1)]
|
range((week_end_date - week_start_date).days + 1)]
|
||||||
|
|
||||||
# Create or update timesheet lines for each day in the week
|
# Create or update timesheet lines for each day in the week
|
||||||
for date in all_week_dates:
|
for date in all_week_dates:
|
||||||
# Check if there is attendance for this date
|
# Check if there is attendance for this date
|
||||||
if date in attendance_by_date:
|
if date in attendance_by_date:
|
||||||
# If there are multiple attendance records, take the earliest check_in and latest check_out
|
# If there are multiple attendance records, take the earliest check_in and latest check_out
|
||||||
earliest_check_in = min(attendance.check_in for attendance in attendance_by_date[date])
|
earliest_check_in = min(attendance.check_in for attendance in attendance_by_date[date])
|
||||||
latest_check_out = max(attendance.check_out for attendance in attendance_by_date[date])
|
latest_check_out = max(attendance.check_out for attendance in attendance_by_date[date])
|
||||||
|
|
||||||
if (earliest_check_in + timedelta(hours=5, minutes=30)).date() > date:
|
if (earliest_check_in + timedelta(hours=5, minutes=30)).date() > date:
|
||||||
earliest_check_in = (datetime.combine(date, datetime.max.time()) - timedelta(hours=5, minutes=30))
|
earliest_check_in = (datetime.combine(date, datetime.max.time()) - timedelta(hours=5, minutes=30))
|
||||||
|
|
||||||
if (latest_check_out + timedelta(hours=5, minutes=30)).date() > date:
|
if (latest_check_out + timedelta(hours=5, minutes=30)).date() > date:
|
||||||
latest_check_out = (datetime.combine(date, datetime.max.time()) - timedelta(hours=5, minutes=30))
|
latest_check_out = (datetime.combine(date, datetime.max.time()) - timedelta(hours=5, minutes=30))
|
||||||
|
|
||||||
# Check if a timesheet line for this employee, week, and date already exists
|
# Check if a timesheet line for this employee, week, and date already exists
|
||||||
existing_timesheet_line = self.env['cwf.timesheet.line'].sudo().search([
|
existing_timesheet_line = self.env['cwf.timesheet.line'].sudo().search([
|
||||||
('week_day', '=', date),
|
('week_day', '=', date),
|
||||||
('employee_id', '=', rec.employee_id.id),
|
('employee_id', '=', rec.employee_id.id),
|
||||||
('week_id', '=', rec.week_id.id),
|
('week_id', '=', rec.week_id.id),
|
||||||
('weekly_timesheet', '=', rec.id)
|
('weekly_timesheet', '=', rec.id)
|
||||||
], limit=1)
|
], limit=1)
|
||||||
|
|
||||||
if existing_timesheet_line:
|
if existing_timesheet_line:
|
||||||
# If it exists, update the existing record
|
# If it exists, update the existing record
|
||||||
existing_timesheet_line.write({
|
existing_timesheet_line.write({
|
||||||
'check_in_date': earliest_check_in,
|
'check_in_date': earliest_check_in,
|
||||||
'check_out_date': latest_check_out,
|
'check_out_date': latest_check_out,
|
||||||
'state_type': 'present',
|
'state_type': 'present',
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
# If it doesn't exist, create a new timesheet line with present state_type
|
# If it doesn't exist, create a new timesheet line with present state_type
|
||||||
self.env['cwf.timesheet.line'].create({
|
self.env['cwf.timesheet.line'].create({
|
||||||
'weekly_timesheet': rec.id,
|
'weekly_timesheet': rec.id,
|
||||||
'employee_id': rec.employee_id.id,
|
'employee_id': rec.employee_id.id,
|
||||||
'week_id': rec.week_id.id,
|
'week_id': rec.week_id.id,
|
||||||
'week_day': date,
|
'week_day': date,
|
||||||
'check_in_date': earliest_check_in,
|
'check_in_date': earliest_check_in,
|
||||||
'check_out_date': latest_check_out,
|
'check_out_date': latest_check_out,
|
||||||
'state_type': 'present',
|
'state_type': 'present',
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
# If no attendance exists for this date, create a new timesheet line with time_off state_type
|
# If no attendance exists for this date, create a new timesheet line with time_off state_type
|
||||||
existing_timesheet_line = self.env['cwf.timesheet.line'].sudo().search([
|
existing_timesheet_line = self.env['cwf.timesheet.line'].sudo().search([
|
||||||
('week_day', '=', date),
|
('week_day', '=', date),
|
||||||
('employee_id', '=', rec.employee_id.id),
|
('employee_id', '=', rec.employee_id.id),
|
||||||
('week_id', '=', rec.week_id.id),
|
('week_id', '=', rec.week_id.id),
|
||||||
('weekly_timesheet', '=', rec.id)
|
('weekly_timesheet', '=', rec.id)
|
||||||
], limit=1)
|
], limit=1)
|
||||||
|
|
||||||
if not existing_timesheet_line:
|
if not existing_timesheet_line:
|
||||||
if date.weekday() != 5 and date.weekday() != 6:
|
if date.weekday() != 5 and date.weekday() != 6:
|
||||||
# If no record exists for this date, create a new timesheet line with time_off state_type
|
# If no record exists for this date, create a new timesheet line with time_off state_type
|
||||||
self.env['cwf.timesheet.line'].create({
|
self.env['cwf.timesheet.line'].create({
|
||||||
'weekly_timesheet': rec.id,
|
'weekly_timesheet': rec.id,
|
||||||
'employee_id': rec.employee_id.id,
|
'employee_id': rec.employee_id.id,
|
||||||
'week_id': rec.week_id.id,
|
'week_id': rec.week_id.id,
|
||||||
'week_day': date,
|
'week_day': date,
|
||||||
'state_type': 'time_off',
|
'state_type': 'time_off',
|
||||||
})
|
})
|
||||||
|
|
||||||
def action_submit(self):
|
def action_submit(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
for timesheet in rec.cwf_timesheet_lines:
|
for timesheet in rec.cwf_timesheet_lines:
|
||||||
timesheet.action_submit()
|
timesheet.action_submit()
|
||||||
rec.status = 'submitted'
|
rec.status = 'submitted'
|
||||||
|
|
||||||
class CwfTimesheetLine(models.Model):
|
class CwfTimesheetLine(models.Model):
|
||||||
_name = 'cwf.timesheet.line'
|
_name = 'cwf.timesheet.line'
|
||||||
_description = 'CWF Weekly Timesheet Lines'
|
_description = 'CWF Weekly Timesheet Lines'
|
||||||
_rec_name = 'employee_id'
|
_rec_name = 'employee_id'
|
||||||
|
|
||||||
weekly_timesheet = fields.Many2one('cwf.weekly.timesheet')
|
weekly_timesheet = fields.Many2one('cwf.weekly.timesheet')
|
||||||
employee_id = fields.Many2one('hr.employee', string='Employee', related='weekly_timesheet.employee_id')
|
employee_id = fields.Many2one('hr.employee', string='Employee', related='weekly_timesheet.employee_id')
|
||||||
week_id = fields.Many2one('cwf.timesheet', 'Week', related='weekly_timesheet.week_id')
|
week_id = fields.Many2one('cwf.timesheet', 'Week', related='weekly_timesheet.week_id')
|
||||||
week_day = fields.Date(string='Date')
|
week_day = fields.Date(string='Date')
|
||||||
check_in_date = fields.Datetime(string='Checkin')
|
check_in_date = fields.Datetime(string='Checkin')
|
||||||
check_out_date = fields.Datetime(string='Checkout ')
|
check_out_date = fields.Datetime(string='Checkout ')
|
||||||
is_updated = fields.Boolean('Attendance Updated')
|
is_updated = fields.Boolean('Attendance Updated')
|
||||||
state_type = fields.Selection([('draft','Draft'),('holiday', 'Holiday'),('time_off','Time Off'),('half_day','Half Day'),('present','Present')], default='draft', required=True)
|
state_type = fields.Selection([('draft','Draft'),('holiday', 'Holiday'),('time_off','Time Off'),('half_day','Half Day'),('present','Present')], default='draft', required=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@api.constrains('week_day', 'check_in_date', 'check_out_date')
|
@api.constrains('week_day', 'check_in_date', 'check_out_date')
|
||||||
def _check_week_day_and_times(self):
|
def _check_week_day_and_times(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
# Ensure week_day is within the week range
|
# Ensure week_day is within the week range
|
||||||
if record.week_id:
|
if record.week_id:
|
||||||
if record.week_day < record.week_id.week_start_date or record.week_day > record.week_id.week_end_date:
|
if record.week_day < record.week_id.week_start_date or record.week_day > record.week_id.week_end_date:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
"The selected 'week_day' must be within the range of the week from %s to %s." %
|
"The selected 'week_day' must be within the range of the week from %s to %s." %
|
||||||
(record.week_id.week_start_date, record.week_id.week_end_date)
|
(record.week_id.week_start_date, record.week_id.week_end_date)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure check_in_date and check_out_date are on the selected week_day
|
# Ensure check_in_date and check_out_date are on the selected week_day
|
||||||
if record.check_in_date:
|
if record.check_in_date:
|
||||||
if record.check_in_date.date() != record.week_day:
|
if record.check_in_date.date() != record.week_day:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
"The 'check_in_date' must be on the selected Date."
|
"The 'check_in_date' must be on the selected Date."
|
||||||
)
|
)
|
||||||
|
|
||||||
if record.check_out_date:
|
if record.check_out_date:
|
||||||
if record.check_out_date.date() != record.week_day:
|
if record.check_out_date.date() != record.week_day:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
"The 'check_out_date' must be on the selected Date."
|
"The 'check_out_date' must be on the selected Date."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def action_submit(self):
|
def action_submit(self):
|
||||||
if self.state_type == 'draft' or not self.state_type:
|
if self.state_type == 'draft' or not self.state_type:
|
||||||
raise ValidationError(_('State type should not Draft or Empty'))
|
raise ValidationError(_('State type should not Draft or Empty'))
|
||||||
if self.state_type not in ['holiday','time_off'] and not (self.check_in_date or self.check_out_date):
|
if self.state_type not in ['holiday','time_off'] and not (self.check_in_date or self.check_out_date):
|
||||||
raise ValidationError(_('Please enter Check details'))
|
raise ValidationError(_('Please enter Check details'))
|
||||||
self._update_attendance()
|
self._update_attendance()
|
||||||
|
|
||||||
|
|
||||||
def _update_attendance(self):
|
def _update_attendance(self):
|
||||||
attendance_obj = self.env['hr.attendance']
|
attendance_obj = self.env['hr.attendance']
|
||||||
for record in self:
|
for record in self:
|
||||||
if record.check_in_date != False and record.check_out_date != False and record.employee_id:
|
if record.check_in_date != False and record.check_out_date != False and record.employee_id:
|
||||||
first_check_in = attendance_obj.sudo().search([('check_in', '>=', record.check_in_date.date()),
|
first_check_in = attendance_obj.sudo().search([('check_in', '>=', record.check_in_date.date()),
|
||||||
('check_out', '<=', record.check_out_date.date()),('employee_id','=',record.employee_id.id)],
|
('check_out', '<=', record.check_out_date.date()),('employee_id','=',record.employee_id.id)],
|
||||||
limit=1, order="check_in")
|
limit=1, order="check_in")
|
||||||
last_check_out = attendance_obj.sudo().search([('check_in', '>=', record.check_in_date.date()),
|
last_check_out = attendance_obj.sudo().search([('check_in', '>=', record.check_in_date.date()),
|
||||||
('check_out', '<=', record.check_out_date.date()),('employee_id','=',record.employee_id.id)],
|
('check_out', '<=', record.check_out_date.date()),('employee_id','=',record.employee_id.id)],
|
||||||
limit=1, order="check_out desc")
|
limit=1, order="check_out desc")
|
||||||
|
|
||||||
if first_check_in or last_check_out:
|
if first_check_in or last_check_out:
|
||||||
if first_check_in.sudo().check_in != record.check_in_date:
|
if first_check_in.sudo().check_in != record.check_in_date:
|
||||||
first_check_in.sudo().check_in = record.check_in_date
|
first_check_in.sudo().check_in = record.check_in_date
|
||||||
if last_check_out.sudo().check_out != record.check_out_date:
|
if last_check_out.sudo().check_out != record.check_out_date:
|
||||||
last_check_out.sudo().check_out = record.check_out_date
|
last_check_out.sudo().check_out = record.check_out_date
|
||||||
else:
|
else:
|
||||||
attendance_obj.sudo().create({
|
attendance_obj.sudo().create({
|
||||||
'employee_id': record.employee_id.id,
|
'employee_id': record.employee_id.id,
|
||||||
'check_in': record.check_in_date - timedelta(hours=5, minutes=30),
|
'check_in': record.check_in_date - timedelta(hours=5, minutes=30),
|
||||||
'check_out': record.check_out_date - timedelta(hours=5, minutes=30),
|
'check_out': record.check_out_date - timedelta(hours=5, minutes=30),
|
||||||
})
|
})
|
||||||
record.is_updated = True
|
record.is_updated = True
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
access_cwf_timesheet_user,access.cwf.timesheet,model_cwf_timesheet,base.group_user,1,0,0,0
|
access_cwf_timesheet_user,access.cwf.timesheet,model_cwf_timesheet,base.group_user,1,0,0,0
|
||||||
access_cwf_timesheet_manager,access.cwf.timesheet,model_cwf_timesheet,hr_attendance.group_hr_attendance_manager,1,1,1,1
|
access_cwf_timesheet_manager,access.cwf.timesheet,model_cwf_timesheet,hr_attendance.group_hr_attendance_manager,1,1,1,1
|
||||||
|
|
||||||
|
|
||||||
access_cwf_timesheet_calendar,cwf_timesheet_calendar,model_cwf_timesheet_calendar,hr_attendance.group_hr_attendance_manager,1,1,1,1
|
access_cwf_timesheet_calendar,cwf_timesheet_calendar,model_cwf_timesheet_calendar,hr_attendance.group_hr_attendance_manager,1,1,1,1
|
||||||
access_cwf_timesheet_calendar_user,cwf_timesheet_calendar_user,model_cwf_timesheet_calendar,base.group_user,1,0,0,0
|
access_cwf_timesheet_calendar_user,cwf_timesheet_calendar_user,model_cwf_timesheet_calendar,base.group_user,1,0,0,0
|
||||||
|
|
||||||
|
|
||||||
access_cwf_timesheet_line_manager,access.cwf.timesheet.line.manager,model_cwf_timesheet_line,hr_attendance.group_hr_attendance_manager,1,1,1,1
|
access_cwf_timesheet_line_manager,access.cwf.timesheet.line.manager,model_cwf_timesheet_line,hr_attendance.group_hr_attendance_manager,1,1,1,1
|
||||||
access_cwf_timesheet_line_user,access.cwf.timesheet.line,model_cwf_timesheet_line,hr_employee_extended.group_external_user,1,1,1,1
|
access_cwf_timesheet_line_user,access.cwf.timesheet.line,model_cwf_timesheet_line,hr_employee_extended.group_external_user,1,1,1,1
|
||||||
|
|
||||||
|
|
||||||
access_cwf_weekly_timesheet_manager,cwf.weekly.timesheet.manager access,model_cwf_weekly_timesheet,hr_attendance.group_hr_attendance_manager,1,1,1,1
|
access_cwf_weekly_timesheet_manager,cwf.weekly.timesheet.manager access,model_cwf_weekly_timesheet,hr_attendance.group_hr_attendance_manager,1,1,1,1
|
||||||
access_cwf_weekly_timesheet_user,cwf.weekly.timesheet access,model_cwf_weekly_timesheet,hr_employee_extended.group_external_user,1,1,1,0
|
access_cwf_weekly_timesheet_user,cwf.weekly.timesheet access,model_cwf_weekly_timesheet,hr_employee_extended.group_external_user,1,1,1,0
|
||||||
|
|
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<data noupdate="0">
|
<data noupdate="0">
|
||||||
<record id="cwf_weekly_timesheet_user_rule" model="ir.rule">
|
<record id="cwf_weekly_timesheet_user_rule" model="ir.rule">
|
||||||
<field name="name">CWF Weekly Timesheet User Rule</field>
|
<field name="name">CWF Weekly Timesheet User Rule</field>
|
||||||
<field ref="cwf_timesheet.model_cwf_weekly_timesheet" name="model_id"/>
|
<field ref="cwf_timesheet.model_cwf_weekly_timesheet" name="model_id"/>
|
||||||
<field name="domain_force">[('employee_id.user_id.id','=',user.id)]</field>
|
<field name="domain_force">[('employee_id.user_id.id','=',user.id)]</field>
|
||||||
<field name="groups" eval="[(4, ref('hr_employee_extended.group_external_user'))]"/>
|
<field name="groups" eval="[(4, ref('hr_employee_extended.group_external_user'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="cwf_timesheet_line_user_rule" model="ir.rule">
|
<record id="cwf_timesheet_line_user_rule" model="ir.rule">
|
||||||
<field name="name">CWF Timesheet Line User Rule</field>
|
<field name="name">CWF Timesheet Line User Rule</field>
|
||||||
<field ref="cwf_timesheet.model_cwf_timesheet_line" name="model_id"/>
|
<field ref="cwf_timesheet.model_cwf_timesheet_line" name="model_id"/>
|
||||||
<field name="domain_force">[('employee_id.user_id.id','=',user.id)]</field>
|
<field name="domain_force">[('employee_id.user_id.id','=',user.id)]</field>
|
||||||
<field name="groups" eval="[(4, ref('hr_employee_extended.group_external_user'))]"/>
|
<field name="groups" eval="[(4, ref('hr_employee_extended.group_external_user'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="cwf_weekly_timesheet_manager_rule" model="ir.rule">
|
<record id="cwf_weekly_timesheet_manager_rule" model="ir.rule">
|
||||||
<field name="name">CWF Weekly Timesheet manager Rule</field>
|
<field name="name">CWF Weekly Timesheet manager Rule</field>
|
||||||
<field ref="cwf_timesheet.model_cwf_weekly_timesheet" name="model_id"/>
|
<field ref="cwf_timesheet.model_cwf_weekly_timesheet" name="model_id"/>
|
||||||
<field name="domain_force">['|',('employee_id.user_id.id','!=',user.id),('employee_id.user_id.id','=',user.id)]</field>
|
<field name="domain_force">['|',('employee_id.user_id.id','!=',user.id),('employee_id.user_id.id','=',user.id)]</field>
|
||||||
<field name="groups" eval="[(4, ref('hr_attendance.group_hr_attendance_manager'))]"/>
|
<field name="groups" eval="[(4, ref('hr_attendance.group_hr_attendance_manager'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="cwf_timesheet_line_manager_rule" model="ir.rule">
|
<record id="cwf_timesheet_line_manager_rule" model="ir.rule">
|
||||||
<field name="name">CWF Timesheet Line manager Rule</field>
|
<field name="name">CWF Timesheet Line manager Rule</field>
|
||||||
<field ref="cwf_timesheet.model_cwf_timesheet_line" name="model_id"/>
|
<field ref="cwf_timesheet.model_cwf_timesheet_line" name="model_id"/>
|
||||||
<field name="domain_force">['|',('employee_id.user_id.id','!=',user.id),('employee_id.user_id.id','=',user.id)]</field>
|
<field name="domain_force">['|',('employee_id.user_id.id','!=',user.id),('employee_id.user_id.id','=',user.id)]</field>
|
||||||
<field name="groups" eval="[(4, ref('hr_attendance.group_hr_attendance_manager'))]"/>
|
<field name="groups" eval="[(4, ref('hr_attendance.group_hr_attendance_manager'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
@ -1,54 +1,54 @@
|
||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
import { patch } from "@web/core/utils/patch";
|
import { patch } from "@web/core/utils/patch";
|
||||||
import { NetflixProfileContainer } from "@hr_emp_dashboard/js/profile_component";
|
import { NetflixProfileContainer } from "@hr_emp_dashboard/js/profile_component";
|
||||||
import { user } from "@web/core/user";
|
import { user } from "@web/core/user";
|
||||||
|
|
||||||
// Apply patch to NetflixProfileContainer prototype
|
// Apply patch to NetflixProfileContainer prototype
|
||||||
patch(NetflixProfileContainer.prototype, {
|
patch(NetflixProfileContainer.prototype, {
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
setup() {
|
setup() {
|
||||||
// Call parent setup method
|
// Call parent setup method
|
||||||
|
|
||||||
super.setup(...arguments);
|
super.setup(...arguments);
|
||||||
|
|
||||||
// Log the department of the logged-in employee (check if data is available)
|
// Log the department of the logged-in employee (check if data is available)
|
||||||
// if (this.state && this.state.login_employee) {
|
// if (this.state && this.state.login_employee) {
|
||||||
// console.log(this.state.login_employee['department_id']);
|
// console.log(this.state.login_employee['department_id']);
|
||||||
// } else {
|
// } else {
|
||||||
// console.error('Employee or department data is unavailable.');
|
// console.error('Employee or department data is unavailable.');
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the hr_timesheets method
|
* Override the hr_timesheets method
|
||||||
*/
|
*/
|
||||||
async hr_timesheets() {
|
async hr_timesheets() {
|
||||||
const isExternalUser = await user.hasGroup("hr_employee_extended.group_external_user");
|
const isExternalUser = await user.hasGroup("hr_employee_extended.group_external_user");
|
||||||
// Check the department of the logged-in employee
|
// Check the department of the logged-in employee
|
||||||
console.log(isExternalUser);
|
console.log(isExternalUser);
|
||||||
console.log("is external user");
|
console.log("is external user");
|
||||||
debugger;
|
debugger;
|
||||||
if (isExternalUser && this.state.login_employee.department_id) {
|
if (isExternalUser && this.state.login_employee.department_id) {
|
||||||
console.log("hello external");
|
console.log("hello external");
|
||||||
// If the department is 'CWF', perform the action to open the timesheets
|
// If the department is 'CWF', perform the action to open the timesheets
|
||||||
this.action.doAction({
|
this.action.doAction({
|
||||||
name: "Timesheets",
|
name: "Timesheets",
|
||||||
type: 'ir.actions.act_window',
|
type: 'ir.actions.act_window',
|
||||||
res_model: 'cwf.timesheet.line', // Ensure this model exists
|
res_model: 'cwf.timesheet.line', // Ensure this model exists
|
||||||
view_mode: 'list,form',
|
view_mode: 'list,form',
|
||||||
views: [[false, 'list'], [false, 'form']],
|
views: [[false, 'list'], [false, 'form']],
|
||||||
context: {
|
context: {
|
||||||
'search_default_week_id': true,
|
'search_default_week_id': true,
|
||||||
},
|
},
|
||||||
domain: [['employee_id.user_id','=', this.props.action.context.user_id]],
|
domain: [['employee_id.user_id','=', this.props.action.context.user_id]],
|
||||||
target: 'current',
|
target: 'current',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// If not 'CWF', call the base functionality
|
// If not 'CWF', call the base functionality
|
||||||
return super.hr_timesheets();
|
return super.hr_timesheets();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<templates id="template" xml:space="preserve">
|
<templates id="template" xml:space="preserve">
|
||||||
<t t-name="timesheet_form">
|
<t t-name="timesheet_form">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Weekly Timesheet</h2>
|
<h2>Weekly Timesheet</h2>
|
||||||
<form class="timesheet-form">
|
<form class="timesheet-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="employee">Employee</label>
|
<label for="employee">Employee</label>
|
||||||
<input t-att-value="state.employee" type="text" id="employee" class="form-control"/>
|
<input t-att-value="state.employee" type="text" id="employee" class="form-control"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="weekStartDate">Week Start Date</label>
|
<label for="weekStartDate">Week Start Date</label>
|
||||||
<input type="datetime-local" t-model="state.weekStartDate" id="weekStartDate" class="form-control"/>
|
<input type="datetime-local" t-model="state.weekStartDate" id="weekStartDate" class="form-control"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="weekEndDate">Week End Date</label>
|
<label for="weekEndDate">Week End Date</label>
|
||||||
<input type="datetime-local" t-model="state.weekEndDate" id="weekEndDate" class="form-control"/>
|
<input type="datetime-local" t-model="state.weekEndDate" id="weekEndDate" class="form-control"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="totalHours">Total Hours Worked</label>
|
<label for="totalHours">Total Hours Worked</label>
|
||||||
<input type="number" t-model="state.totalHours" id="totalHours" class="form-control" min="0"/>
|
<input type="number" t-model="state.totalHours" id="totalHours" class="form-control" min="0"/>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" t-on-click="submitForm" class="btn btn-primary">Submit Timesheet</button>
|
<button type="button" t-on-click="submitForm" class="btn btn-primary">Submit Timesheet</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
</templates>
|
</templates>
|
||||||
|
|
|
||||||
|
|
@ -1,104 +1,104 @@
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="view_cwf_timesheet_calendar_form" model="ir.ui.view">
|
<record id="view_cwf_timesheet_calendar_form" model="ir.ui.view">
|
||||||
<field name="name">cwf.timesheet.calendar.form</field>
|
<field name="name">cwf.timesheet.calendar.form</field>
|
||||||
<field name="model">cwf.timesheet.calendar</field>
|
<field name="model">cwf.timesheet.calendar</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="CWF Timesheet Calendar">
|
<form string="CWF Timesheet Calendar">
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<button name="action_generate_weeks" string="Generate Week Periods" type="object"
|
<button name="action_generate_weeks" string="Generate Week Periods" type="object"
|
||||||
class="oe_highlight"/>
|
class="oe_highlight"/>
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Weeks">
|
<page string="Weeks">
|
||||||
<field name="week_period" context="{'order': 'week_start_date asc'}">
|
<field name="week_period" context="{'order': 'week_start_date asc'}">
|
||||||
<list editable="bottom" decoration-success="status == 'submitted'">
|
<list editable="bottom" decoration-success="status == 'submitted'">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="week_start_date"/>
|
<field name="week_start_date"/>
|
||||||
<field name="week_end_date"/>
|
<field name="week_end_date"/>
|
||||||
<field name="start_month" column_invisible="1"/>
|
<field name="start_month" column_invisible="1"/>
|
||||||
<field name="end_month" column_invisible="1"/>
|
<field name="end_month" column_invisible="1"/>
|
||||||
<field name="status"/>
|
<field name="status"/>
|
||||||
<button name="send_timesheet_update_email" string="Send Update Email"
|
<button name="send_timesheet_update_email" string="Send Update Email"
|
||||||
invisible="status == 'submitted'" type="object"
|
invisible="status == 'submitted'" type="object"
|
||||||
confirm="You can't revert this action. Please check twice before Submitting?"
|
confirm="You can't revert this action. Please check twice before Submitting?"
|
||||||
class="oe_highlight"/>
|
class="oe_highlight"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="view_cwf_timesheet_calendar_list" model="ir.ui.view">
|
<record id="view_cwf_timesheet_calendar_list" model="ir.ui.view">
|
||||||
<field name="name">cwf.timesheet.calendar.list</field>
|
<field name="name">cwf.timesheet.calendar.list</field>
|
||||||
<field name="model">cwf.timesheet.calendar</field>
|
<field name="model">cwf.timesheet.calendar</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list>
|
<list>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="action_cwf_timesheet_calendar" model="ir.actions.act_window">
|
<record id="action_cwf_timesheet_calendar" model="ir.actions.act_window">
|
||||||
<field name="name">CWF Timesheet Calendar</field>
|
<field name="name">CWF Timesheet Calendar</field>
|
||||||
<field name="res_model">cwf.timesheet.calendar</field>
|
<field name="res_model">cwf.timesheet.calendar</field>
|
||||||
<field name="view_mode">list,form</field>
|
<field name="view_mode">list,form</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<menuitem id="menu_cwf_attendance_attendance" name="CWF" parent="hr_attendance.menu_hr_attendance_root"
|
<menuitem id="menu_cwf_attendance_attendance" name="CWF" parent="hr_attendance.menu_hr_attendance_root"
|
||||||
sequence="6" groups="hr_employee_extended.group_external_user,hr_attendance.group_hr_attendance_manager"/>
|
sequence="6" groups="hr_employee_extended.group_external_user,hr_attendance.group_hr_attendance_manager"/>
|
||||||
|
|
||||||
<menuitem id="menu_timesheet_calendar_form" name="CWF Timesheet Calendar" parent="menu_cwf_attendance_attendance"
|
<menuitem id="menu_timesheet_calendar_form" name="CWF Timesheet Calendar" parent="menu_cwf_attendance_attendance"
|
||||||
action="cwf_timesheet.action_cwf_timesheet_calendar" groups="hr_attendance.group_hr_attendance_manager"/>
|
action="cwf_timesheet.action_cwf_timesheet_calendar" groups="hr_attendance.group_hr_attendance_manager"/>
|
||||||
|
|
||||||
|
|
||||||
<record id="view_timesheet_form" model="ir.ui.view">
|
<record id="view_timesheet_form" model="ir.ui.view">
|
||||||
<field name="name">cwf.timesheet.form</field>
|
<field name="name">cwf.timesheet.form</field>
|
||||||
<field name="model">cwf.timesheet</field>
|
<field name="model">cwf.timesheet</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Timesheet">
|
<form string="Timesheet">
|
||||||
<header>
|
<header>
|
||||||
<button name="send_timesheet_update_email" string="Send Email" type="object"
|
<button name="send_timesheet_update_email" string="Send Email" type="object"
|
||||||
invisible="status != 'draft'"/>
|
invisible="status != 'draft'"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<div class="oe_title">
|
<div class="oe_title">
|
||||||
<label for="name"/>
|
<label for="name"/>
|
||||||
<h1>
|
<h1>
|
||||||
<field name="name" class="oe_inline" readonly="status != 'draft'"/>
|
<field name="name" class="oe_inline" readonly="status != 'draft'"/>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<!-- Section for Employee and Date Range -->
|
<!-- Section for Employee and Date Range -->
|
||||||
<group>
|
<group>
|
||||||
<field name="week_start_date" readonly="status != 'draft'"/>
|
<field name="week_start_date" readonly="status != 'draft'"/>
|
||||||
<field name="week_end_date" readonly="status != 'draft'"/>
|
<field name="week_end_date" readonly="status != 'draft'"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</sheet>
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="view_timesheet_list" model="ir.ui.view">
|
<record id="view_timesheet_list" model="ir.ui.view">
|
||||||
<field name="name">cwf.timesheet.list</field>
|
<field name="name">cwf.timesheet.list</field>
|
||||||
<field name="model">cwf.timesheet</field>
|
<field name="model">cwf.timesheet</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list>
|
<list>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="week_start_date"/>
|
<field name="week_start_date"/>
|
||||||
<field name="week_end_date"/>
|
<field name="week_end_date"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="action_cwf_timesheet" model="ir.actions.act_window">
|
<record id="action_cwf_timesheet" model="ir.actions.act_window">
|
||||||
<field name="name">CWF Timesheet</field>
|
<field name="name">CWF Timesheet</field>
|
||||||
<field name="res_model">cwf.timesheet</field>
|
<field name="res_model">cwf.timesheet</field>
|
||||||
<field name="view_mode">list,form</field>
|
<field name="view_mode">list,form</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,158 +1,158 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="view_cwf_weekly_timesheet_form" model="ir.ui.view">
|
<record id="view_cwf_weekly_timesheet_form" model="ir.ui.view">
|
||||||
<field name="name">cwf.weekly.timesheet.form</field>
|
<field name="name">cwf.weekly.timesheet.form</field>
|
||||||
<field name="model">cwf.weekly.timesheet</field>
|
<field name="model">cwf.weekly.timesheet</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="CWF Weekly Timesheet">
|
<form string="CWF Weekly Timesheet">
|
||||||
<header>
|
<header>
|
||||||
<button name="update_attendance" string="Update"
|
<button name="update_attendance" string="Update"
|
||||||
type="object" class="oe_highlight" invisible="status != 'draft'"/>
|
type="object" class="oe_highlight" invisible="status != 'draft'"/>
|
||||||
<button name="action_submit" string="Submit" type="object" confirm="Are you sure you want to submit?"
|
<button name="action_submit" string="Submit" type="object" confirm="Are you sure you want to submit?"
|
||||||
class="oe_highlight" invisible="status != 'draft'"/>
|
class="oe_highlight" invisible="status != 'draft'"/>
|
||||||
<field name="status" readonly="1" widget="statusbar"/>
|
<field name="status" readonly="1" widget="statusbar"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
<field name="month_id"/>
|
<field name="month_id"/>
|
||||||
<field name="week_id" readonly="0" domain="['|',('start_month','=',month_id),('end_month','=',month_id)]"/>
|
<field name="week_id" readonly="0" domain="['|',('start_month','=',month_id),('end_month','=',month_id)]"/>
|
||||||
<field name="employee_id" readonly="0" groups="hr_attendance.group_hr_attendance_manager"/>
|
<field name="employee_id" readonly="0" groups="hr_attendance.group_hr_attendance_manager"/>
|
||||||
<field name="employee_id" readonly="1" groups="hr_employee_extended.group_external_user"/>
|
<field name="employee_id" readonly="1" groups="hr_employee_extended.group_external_user"/>
|
||||||
<label for="week_start_date" string="Dates"/>
|
<label for="week_start_date" string="Dates"/>
|
||||||
<div class="o_row">
|
<div class="o_row">
|
||||||
<field name="week_start_date" widget="daterange" options="{'end_date_field.month()': 'week_end_date'}"/>
|
<field name="week_start_date" widget="daterange" options="{'end_date_field.month()': 'week_end_date'}"/>
|
||||||
<field name="week_end_date" invisible="1"/>
|
<field name="week_end_date" invisible="1"/>
|
||||||
</div>
|
</div>
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Timesheet Lines">
|
<page string="Timesheet Lines">
|
||||||
<field name="cwf_timesheet_lines">
|
<field name="cwf_timesheet_lines">
|
||||||
<list editable="bottom">
|
<list editable="bottom">
|
||||||
<field name="employee_id"/>
|
<field name="employee_id"/>
|
||||||
<field name="week_day"/>
|
<field name="week_day"/>
|
||||||
<field name="check_in_date"/>
|
<field name="check_in_date"/>
|
||||||
<field name="check_out_date"/>
|
<field name="check_out_date"/>
|
||||||
<field name="state_type"/>
|
<field name="state_type"/>
|
||||||
<!-- <button name="action_submit" string="Submit" type="object"-->
|
<!-- <button name="action_submit" string="Submit" type="object"-->
|
||||||
<!-- confirm="Are you sure you want to submit?" class="oe_highlight"/>-->
|
<!-- confirm="Are you sure you want to submit?" class="oe_highlight"/>-->
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="view_cwf_weekly_timesheet_list" model="ir.ui.view">
|
<record id="view_cwf_weekly_timesheet_list" model="ir.ui.view">
|
||||||
<field name="name">cwf.weekly.timesheet.list</field>
|
<field name="name">cwf.weekly.timesheet.list</field>
|
||||||
<field name="model">cwf.weekly.timesheet</field>
|
<field name="model">cwf.weekly.timesheet</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list>
|
<list>
|
||||||
<field name="week_id"/>
|
<field name="week_id"/>
|
||||||
<field name="employee_id"/>
|
<field name="employee_id"/>
|
||||||
<field name="status"/>
|
<field name="status"/>
|
||||||
<!-- <button name="action_submit" string="Submit" type="object" confirm="Are you sure you want to submit?"-->
|
<!-- <button name="action_submit" string="Submit" type="object" confirm="Are you sure you want to submit?"-->
|
||||||
<!-- class="oe_highlight"/>-->
|
<!-- class="oe_highlight"/>-->
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="view_cwf_weekly_timesheet_search" model="ir.ui.view">
|
<record id="view_cwf_weekly_timesheet_search" model="ir.ui.view">
|
||||||
<field name="name">cwf.weekly.timesheet.search</field>
|
<field name="name">cwf.weekly.timesheet.search</field>
|
||||||
<field name="model">cwf.weekly.timesheet</field>
|
<field name="model">cwf.weekly.timesheet</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<search>
|
<search>
|
||||||
<!-- Search by Week ID -->
|
<!-- Search by Week ID -->
|
||||||
<field name="week_id"/>
|
<field name="week_id"/>
|
||||||
|
|
||||||
<!-- Search by Employee -->
|
<!-- Search by Employee -->
|
||||||
<field name="employee_id"/>
|
<field name="employee_id"/>
|
||||||
|
|
||||||
<!-- Search by Status -->
|
<!-- Search by Status -->
|
||||||
<field name="status"/>
|
<field name="status"/>
|
||||||
|
|
||||||
<!-- Optional: Add custom filters if needed -->
|
<!-- Optional: Add custom filters if needed -->
|
||||||
<filter string="Draft" name="filter_draft" domain="[('status','=','draft')]"/>
|
<filter string="Draft" name="filter_draft" domain="[('status','=','draft')]"/>
|
||||||
<filter string="Submitted" name="filter_submitted" domain="[('status','=','submit')]"/>
|
<filter string="Submitted" name="filter_submitted" domain="[('status','=','submit')]"/>
|
||||||
<group expand="0" string="Group By">
|
<group expand="0" string="Group By">
|
||||||
<filter string="Week" name="by_week_id" domain="[]" context="{'group_by':'week_id'}"/>
|
<filter string="Week" name="by_week_id" domain="[]" context="{'group_by':'week_id'}"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="Employee" name="by_employee_id" domain="[]" context="{'group_by':'employee_id'}"/>
|
<filter string="Employee" name="by_employee_id" domain="[]" context="{'group_by':'employee_id'}"/>
|
||||||
<filter string="Status" name="state_type" domain="[]" context="{'group_by': 'status'}"/>
|
<filter string="Status" name="state_type" domain="[]" context="{'group_by': 'status'}"/>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
</search>
|
</search>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
<record id="action_cwf_weekly_timesheet" model="ir.actions.act_window">
|
<record id="action_cwf_weekly_timesheet" model="ir.actions.act_window">
|
||||||
<field name="name">CWF Weekly Timesheet</field>
|
<field name="name">CWF Weekly Timesheet</field>
|
||||||
<field name="res_model">cwf.weekly.timesheet</field>
|
<field name="res_model">cwf.weekly.timesheet</field>
|
||||||
<field name="view_mode">list,form</field>
|
<field name="view_mode">list,form</field>
|
||||||
<field name="context">
|
<field name="context">
|
||||||
{
|
{
|
||||||
"search_default_by_week_id": 1,
|
"search_default_by_week_id": 1,
|
||||||
"search_default_by_employee_id": 2
|
"search_default_by_employee_id": 2
|
||||||
}
|
}
|
||||||
</field>
|
</field>
|
||||||
|
|
||||||
<field name="search_view_id" ref="cwf_timesheet.view_cwf_weekly_timesheet_search"/>
|
<field name="search_view_id" ref="cwf_timesheet.view_cwf_weekly_timesheet_search"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
<record id="view_cwf_timesheet_line_list" model="ir.ui.view">
|
<record id="view_cwf_timesheet_line_list" model="ir.ui.view">
|
||||||
<field name="name">cwf.timesheet.line.list</field>
|
<field name="name">cwf.timesheet.line.list</field>
|
||||||
<field name="model">cwf.timesheet.line</field>
|
<field name="model">cwf.timesheet.line</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list editable="bottom" create="0" delete="0" decoration-success="is_updated == True">
|
<list editable="bottom" create="0" delete="0" decoration-success="is_updated == True">
|
||||||
<field name="employee_id" readonly="1" force_save="1"/>
|
<field name="employee_id" readonly="1" force_save="1"/>
|
||||||
<field name="week_day" readonly="1" force_save="1"/>
|
<field name="week_day" readonly="1" force_save="1"/>
|
||||||
<field name="check_in_date" readonly="is_updated == True"/>
|
<field name="check_in_date" readonly="is_updated == True"/>
|
||||||
<field name="check_out_date" readonly="is_updated == True"/>
|
<field name="check_out_date" readonly="is_updated == True"/>
|
||||||
<field name="state_type" readonly="is_updated == True"/>
|
<field name="state_type" readonly="is_updated == True"/>
|
||||||
<!-- <button name="action_submit" type="object" string="Submit" class="btn btn-outline-primary"-->
|
<!-- <button name="action_submit" type="object" string="Submit" class="btn btn-outline-primary"-->
|
||||||
<!-- invisible="is_updated == True"/>-->
|
<!-- invisible="is_updated == True"/>-->
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="view_cwf_timesheet_line_search" model="ir.ui.view">
|
<record id="view_cwf_timesheet_line_search" model="ir.ui.view">
|
||||||
<field name="name">cwf.timesheet.line.search</field>
|
<field name="name">cwf.timesheet.line.search</field>
|
||||||
<field name="model">cwf.timesheet.line</field>
|
<field name="model">cwf.timesheet.line</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<search string="Timesheets">
|
<search string="Timesheets">
|
||||||
<field name="employee_id"/>
|
<field name="employee_id"/>
|
||||||
<field name="week_id"/>
|
<field name="week_id"/>
|
||||||
<field name="week_day"/>
|
<field name="week_day"/>
|
||||||
<field name="check_in_date"/>
|
<field name="check_in_date"/>
|
||||||
<field name="check_out_date"/>
|
<field name="check_out_date"/>
|
||||||
<field name="state_type"/>
|
<field name="state_type"/>
|
||||||
<group expand="0" string="Group By">
|
<group expand="0" string="Group By">
|
||||||
<filter string="Employee" name="by_employee_id" domain="[]" context="{'group_by':'employee_id'}"/>
|
<filter string="Employee" name="by_employee_id" domain="[]" context="{'group_by':'employee_id'}"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="Week" name="by_week_id" domain="[]" context="{'group_by':'week_id'}"/>
|
<filter string="Week" name="by_week_id" domain="[]" context="{'group_by':'week_id'}"/>
|
||||||
<filter string="Status" name="state_type" domain="[]" context="{'group_by': 'state_type'}"/>
|
<filter string="Status" name="state_type" domain="[]" context="{'group_by': 'state_type'}"/>
|
||||||
</group>
|
</group>
|
||||||
</search>
|
</search>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
<record id="action_cwf_timesheet_line" model="ir.actions.act_window">
|
<record id="action_cwf_timesheet_line" model="ir.actions.act_window">
|
||||||
<field name="name">CWF Timesheet Lines</field>
|
<field name="name">CWF Timesheet Lines</field>
|
||||||
<field name="res_model">cwf.timesheet.line</field>
|
<field name="res_model">cwf.timesheet.line</field>
|
||||||
<field name="view_mode">list</field>
|
<field name="view_mode">list</field>
|
||||||
<field name="context">{"search_default_by_week_id": 1, "search_default_by_employee_id": 1}</field>
|
<field name="context">{"search_default_by_week_id": 1, "search_default_by_employee_id": 1}</field>
|
||||||
<field name="search_view_id" ref="cwf_timesheet.view_cwf_timesheet_line_search"/>
|
<field name="search_view_id" ref="cwf_timesheet.view_cwf_timesheet_line_search"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<menuitem id="menu_timesheet_form" name="CWF Weekly Timesheet "
|
<menuitem id="menu_timesheet_form" name="CWF Weekly Timesheet "
|
||||||
parent="menu_cwf_attendance_attendance" action="action_cwf_weekly_timesheet" groups="hr_employee_extended.group_external_user,hr_attendance.group_hr_attendance_manager"/>
|
parent="menu_cwf_attendance_attendance" action="action_cwf_weekly_timesheet" groups="hr_employee_extended.group_external_user,hr_attendance.group_hr_attendance_manager"/>
|
||||||
<menuitem id="menu_timesheet_form_line" name="CWF Timesheet Lines"
|
<menuitem id="menu_timesheet_form_line" name="CWF Timesheet Lines"
|
||||||
parent="menu_cwf_attendance_attendance" action="action_cwf_timesheet_line" groups="hr_employee_extended.group_external_user,hr_attendance.group_hr_attendance_manager"/>
|
parent="menu_cwf_attendance_attendance" action="action_cwf_timesheet_line" groups="hr_employee_extended.group_external_user,hr_attendance.group_hr_attendance_manager"/>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from . import controllers
|
from . import controllers
|
||||||
from . import wizard
|
from . import wizard
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,94 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
{
|
{
|
||||||
'name': "Documents",
|
'name': "Documents",
|
||||||
|
|
||||||
'summary': "Collect, organize and share documents.",
|
'summary': "Collect, organize and share documents.",
|
||||||
|
|
||||||
'description': """
|
'description': """
|
||||||
App to upload and manage your documents.
|
App to upload and manage your documents.
|
||||||
""",
|
""",
|
||||||
|
|
||||||
'category': 'Productivity/Documents',
|
'category': 'Productivity/Documents',
|
||||||
'sequence': 80,
|
'sequence': 80,
|
||||||
'version': '1.4',
|
'version': '1.4',
|
||||||
'application': True,
|
'application': True,
|
||||||
'website': 'https://www.ftprotech.in/',
|
'website': 'https://www.ftprotech.in/',
|
||||||
|
|
||||||
# any module necessary for this one to work correctly
|
# any module necessary for this one to work correctly
|
||||||
'depends': ['base', 'mail', 'portal', 'attachment_indexation', 'digest'],
|
'depends': ['base', 'mail', 'portal', 'attachment_indexation', 'digest'],
|
||||||
|
|
||||||
# always loaded
|
# always loaded
|
||||||
'data': [
|
'data': [
|
||||||
'security/security.xml',
|
'security/security.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'data/digest_data.xml',
|
'data/digest_data.xml',
|
||||||
'data/mail_template_data.xml',
|
'data/mail_template_data.xml',
|
||||||
'data/mail_activity_type_data.xml',
|
'data/mail_activity_type_data.xml',
|
||||||
'data/documents_tag_data.xml',
|
'data/documents_tag_data.xml',
|
||||||
'data/documents_document_data.xml',
|
'data/documents_document_data.xml',
|
||||||
'data/ir_config_parameter_data.xml',
|
'data/ir_config_parameter_data.xml',
|
||||||
'data/documents_tour.xml',
|
'data/documents_tour.xml',
|
||||||
'views/res_config_settings_views.xml',
|
'views/res_config_settings_views.xml',
|
||||||
'views/res_partner_views.xml',
|
'views/res_partner_views.xml',
|
||||||
'views/documents_access_views.xml',
|
'views/documents_access_views.xml',
|
||||||
'views/documents_document_views.xml',
|
'views/documents_document_views.xml',
|
||||||
'views/documents_folder_views.xml',
|
'views/documents_folder_views.xml',
|
||||||
'views/documents_tag_views.xml',
|
'views/documents_tag_views.xml',
|
||||||
'views/mail_activity_views.xml',
|
'views/mail_activity_views.xml',
|
||||||
'views/mail_activity_plan_views.xml',
|
'views/mail_activity_plan_views.xml',
|
||||||
'views/mail_alias_views.xml',
|
'views/mail_alias_views.xml',
|
||||||
'views/documents_menu_views.xml',
|
'views/documents_menu_views.xml',
|
||||||
'views/documents_templates_portal.xml',
|
'views/documents_templates_portal.xml',
|
||||||
'views/documents_templates_share.xml',
|
'views/documents_templates_share.xml',
|
||||||
'wizard/documents_link_to_record_wizard_views.xml',
|
'wizard/documents_link_to_record_wizard_views.xml',
|
||||||
'wizard/documents_request_wizard_views.xml',
|
'wizard/documents_request_wizard_views.xml',
|
||||||
# Need the `ir.actions.act_window` to exist
|
# Need the `ir.actions.act_window` to exist
|
||||||
'data/ir_actions_server_data.xml',
|
'data/ir_actions_server_data.xml',
|
||||||
],
|
],
|
||||||
|
|
||||||
'demo': [
|
'demo': [
|
||||||
'demo/documents_document_demo.xml',
|
'demo/documents_document_demo.xml',
|
||||||
],
|
],
|
||||||
'license': 'OEEL-1',
|
'license': 'OEEL-1',
|
||||||
'assets': {
|
'assets': {
|
||||||
'web.assets_backend': [
|
'web.assets_backend': [
|
||||||
'documents/static/src/model/**/*',
|
'documents/static/src/model/**/*',
|
||||||
'documents/static/src/scss/documents_views.scss',
|
'documents/static/src/scss/documents_views.scss',
|
||||||
'documents/static/src/scss/documents_kanban_view.scss',
|
'documents/static/src/scss/documents_kanban_view.scss',
|
||||||
'documents/static/src/attachments/**/*',
|
'documents/static/src/attachments/**/*',
|
||||||
'documents/static/src/core/**/*',
|
'documents/static/src/core/**/*',
|
||||||
'documents/static/src/js/**/*',
|
'documents/static/src/js/**/*',
|
||||||
'documents/static/src/owl/**/*',
|
'documents/static/src/owl/**/*',
|
||||||
'documents/static/src/views/**/*',
|
'documents/static/src/views/**/*',
|
||||||
('remove', 'documents/static/src/views/activity/**'),
|
('remove', 'documents/static/src/views/activity/**'),
|
||||||
('after', 'web/static/src/core/errors/error_dialogs.xml', 'documents/static/src/web/error_dialog/error_dialog_patch.xml'),
|
('after', 'web/static/src/core/errors/error_dialogs.xml', 'documents/static/src/web/error_dialog/error_dialog_patch.xml'),
|
||||||
'documents/static/src/web/**/*',
|
'documents/static/src/web/**/*',
|
||||||
'documents/static/src/components/**/*',
|
'documents/static/src/components/**/*',
|
||||||
],
|
],
|
||||||
'web.assets_backend_lazy': [
|
'web.assets_backend_lazy': [
|
||||||
'documents/static/src/views/activity/**',
|
'documents/static/src/views/activity/**',
|
||||||
],
|
],
|
||||||
'web._assets_primary_variables': [
|
'web._assets_primary_variables': [
|
||||||
'documents/static/src/scss/documents.variables.scss',
|
'documents/static/src/scss/documents.variables.scss',
|
||||||
],
|
],
|
||||||
"web.dark_mode_variables": [
|
"web.dark_mode_variables": [
|
||||||
('before', 'documents/static/src/scss/documents.variables.scss', 'documents/static/src/scss/documents.variables.dark.scss'),
|
('before', 'documents/static/src/scss/documents.variables.scss', 'documents/static/src/scss/documents.variables.dark.scss'),
|
||||||
],
|
],
|
||||||
'documents.public_page_assets': [
|
'documents.public_page_assets': [
|
||||||
('include', 'web._assets_helpers'),
|
('include', 'web._assets_helpers'),
|
||||||
('include', 'web._assets_backend_helpers'),
|
('include', 'web._assets_backend_helpers'),
|
||||||
'web/static/src/scss/pre_variables.scss',
|
'web/static/src/scss/pre_variables.scss',
|
||||||
'web/static/lib/bootstrap/scss/_variables.scss',
|
'web/static/lib/bootstrap/scss/_variables.scss',
|
||||||
'web/static/lib/bootstrap/scss/_variables-dark.scss',
|
'web/static/lib/bootstrap/scss/_variables-dark.scss',
|
||||||
'web/static/lib/bootstrap/scss/_maps.scss',
|
'web/static/lib/bootstrap/scss/_maps.scss',
|
||||||
('include', 'web._assets_bootstrap_backend'),
|
('include', 'web._assets_bootstrap_backend'),
|
||||||
'documents/static/src/scss/documents_public_pages.scss',
|
'documents/static/src/scss/documents_public_pages.scss',
|
||||||
],
|
],
|
||||||
'documents.webclient': [
|
'documents.webclient': [
|
||||||
('include', 'web.assets_backend'),
|
('include', 'web.assets_backend'),
|
||||||
# documents webclient overrides
|
# documents webclient overrides
|
||||||
'documents/static/src/portal_webclient/**/*',
|
'documents/static/src/portal_webclient/**/*',
|
||||||
'web/static/src/start.js',
|
'web/static/src/start.js',
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from . import documents
|
from . import documents
|
||||||
from . import home
|
from . import home
|
||||||
from . import portal
|
from . import portal
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,86 +1,86 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from odoo.http import request, route
|
from odoo.http import request, route
|
||||||
|
|
||||||
from odoo.addons.web.controllers import home as web_home
|
from odoo.addons.web.controllers import home as web_home
|
||||||
from odoo.addons.web.controllers.utils import ensure_db
|
from odoo.addons.web.controllers.utils import ensure_db
|
||||||
from .documents import ShareRoute
|
from .documents import ShareRoute
|
||||||
|
|
||||||
|
|
||||||
class Home(web_home.Home):
|
class Home(web_home.Home):
|
||||||
def _web_client_readonly(self):
|
def _web_client_readonly(self):
|
||||||
""" Force a read/write cursor for documents.access """
|
""" Force a read/write cursor for documents.access """
|
||||||
path = request.httprequest.path
|
path = request.httprequest.path
|
||||||
if (
|
if (
|
||||||
path.startswith('/odoo/documents')
|
path.startswith('/odoo/documents')
|
||||||
and (request.httprequest.args.get('access_token') or path.removeprefix('/odoo/documents/'))
|
and (request.httprequest.args.get('access_token') or path.removeprefix('/odoo/documents/'))
|
||||||
and request.session.uid
|
and request.session.uid
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
return super()._web_client_readonly()
|
return super()._web_client_readonly()
|
||||||
|
|
||||||
@route(readonly=_web_client_readonly)
|
@route(readonly=_web_client_readonly)
|
||||||
def web_client(self, s_action=None, **kw):
|
def web_client(self, s_action=None, **kw):
|
||||||
""" Handle direct access to a document with a backend URL (/odoo/documents/<access_token>).
|
""" Handle direct access to a document with a backend URL (/odoo/documents/<access_token>).
|
||||||
|
|
||||||
It redirects to the document either in:
|
It redirects to the document either in:
|
||||||
- the backend if the user is logged and has access to the Documents module
|
- the backend if the user is logged and has access to the Documents module
|
||||||
- or a lightweight version of the backend if the user is logged and has not access
|
- or a lightweight version of the backend if the user is logged and has not access
|
||||||
to the Document module but well to the documents.document model
|
to the Document module but well to the documents.document model
|
||||||
- or the document portal otherwise
|
- or the document portal otherwise
|
||||||
|
|
||||||
Goal: Allow to share directly the backend URL of a document.
|
Goal: Allow to share directly the backend URL of a document.
|
||||||
"""
|
"""
|
||||||
subpath = kw.get('subpath', '')
|
subpath = kw.get('subpath', '')
|
||||||
access_token = request.params.get('access_token') or subpath.removeprefix('documents/')
|
access_token = request.params.get('access_token') or subpath.removeprefix('documents/')
|
||||||
if not subpath.startswith('documents') or not access_token or '/' in access_token:
|
if not subpath.startswith('documents') or not access_token or '/' in access_token:
|
||||||
return super().web_client(s_action, **kw)
|
return super().web_client(s_action, **kw)
|
||||||
|
|
||||||
# This controller should be auth='public' but it actually is
|
# This controller should be auth='public' but it actually is
|
||||||
# auth='none' for technical reasons (see super). Those three
|
# auth='none' for technical reasons (see super). Those three
|
||||||
# lines restore the public behavior.
|
# lines restore the public behavior.
|
||||||
ensure_db()
|
ensure_db()
|
||||||
request.update_env(user=request.session.uid)
|
request.update_env(user=request.session.uid)
|
||||||
request.env['ir.http']._authenticate_explicit('public')
|
request.env['ir.http']._authenticate_explicit('public')
|
||||||
|
|
||||||
# Public/Portal users use the /documents/<access_token> route
|
# Public/Portal users use the /documents/<access_token> route
|
||||||
if not request.env.user._is_internal():
|
if not request.env.user._is_internal():
|
||||||
return request.redirect(
|
return request.redirect(
|
||||||
f'/documents/{access_token}',
|
f'/documents/{access_token}',
|
||||||
HTTPStatus.TEMPORARY_REDIRECT,
|
HTTPStatus.TEMPORARY_REDIRECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
document_sudo = ShareRoute._from_access_token(access_token, follow_shortcut=False)
|
document_sudo = ShareRoute._from_access_token(access_token, follow_shortcut=False)
|
||||||
|
|
||||||
if not document_sudo:
|
if not document_sudo:
|
||||||
Redirect = request.env['documents.redirect'].sudo()
|
Redirect = request.env['documents.redirect'].sudo()
|
||||||
if document_sudo := Redirect._get_redirection(access_token):
|
if document_sudo := Redirect._get_redirection(access_token):
|
||||||
return request.redirect(
|
return request.redirect(
|
||||||
f'/odoo/documents/{document_sudo.access_token}',
|
f'/odoo/documents/{document_sudo.access_token}',
|
||||||
HTTPStatus.MOVED_PERMANENTLY,
|
HTTPStatus.MOVED_PERMANENTLY,
|
||||||
)
|
)
|
||||||
|
|
||||||
# We want (1) the webclient renders the webclient template and load
|
# We want (1) the webclient renders the webclient template and load
|
||||||
# the document action. We also want (2) the router rewrites
|
# the document action. We also want (2) the router rewrites
|
||||||
# /odoo/documents/<id> to /odoo/documents/<access-token> in the
|
# /odoo/documents/<id> to /odoo/documents/<access-token> in the
|
||||||
# URL.
|
# URL.
|
||||||
# We redirect on /web so this override does kicks in again,
|
# We redirect on /web so this override does kicks in again,
|
||||||
# super() is loaded and renders the normal home template. We add
|
# super() is loaded and renders the normal home template. We add
|
||||||
# custom fragments so we can load them inside the router and
|
# custom fragments so we can load them inside the router and
|
||||||
# rewrite the URL.
|
# rewrite the URL.
|
||||||
query = {}
|
query = {}
|
||||||
if request.session.debug:
|
if request.session.debug:
|
||||||
query['debug'] = request.session.debug
|
query['debug'] = request.session.debug
|
||||||
fragment = {
|
fragment = {
|
||||||
'action': request.env.ref("documents.document_action").id,
|
'action': request.env.ref("documents.document_action").id,
|
||||||
'menu_id': request.env.ref('documents.menu_root').id,
|
'menu_id': request.env.ref('documents.menu_root').id,
|
||||||
'model': 'documents.document',
|
'model': 'documents.document',
|
||||||
}
|
}
|
||||||
if document_sudo:
|
if document_sudo:
|
||||||
fragment.update({
|
fragment.update({
|
||||||
f'documents_init_{key}': value
|
f'documents_init_{key}': value
|
||||||
for key, value
|
for key, value
|
||||||
in ShareRoute._documents_get_init_data(document_sudo, request.env.user).items()
|
in ShareRoute._documents_get_init_data(document_sudo, request.env.user).items()
|
||||||
})
|
})
|
||||||
return request.redirect(f'/web?{urlencode(query)}#{urlencode(fragment)}')
|
return request.redirect(f'/web?{urlencode(query)}#{urlencode(fragment)}')
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo.addons.portal.controllers.portal import CustomerPortal
|
from odoo.addons.portal.controllers.portal import CustomerPortal
|
||||||
from odoo.exceptions import AccessError
|
from odoo.exceptions import AccessError
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
|
|
||||||
|
|
||||||
class DocumentCustomerPortal(CustomerPortal):
|
class DocumentCustomerPortal(CustomerPortal):
|
||||||
|
|
||||||
def _prepare_home_portal_values(self, counters):
|
def _prepare_home_portal_values(self, counters):
|
||||||
values = super()._prepare_home_portal_values(counters)
|
values = super()._prepare_home_portal_values(counters)
|
||||||
if 'document_count' in counters:
|
if 'document_count' in counters:
|
||||||
Document = request.env['documents.document']
|
Document = request.env['documents.document']
|
||||||
try:
|
try:
|
||||||
count = Document.search_count([])
|
count = Document.search_count([])
|
||||||
except AccessError:
|
except AccessError:
|
||||||
count = 0
|
count = 0
|
||||||
values['document_count'] = count
|
values['document_count'] = count
|
||||||
return values
|
return values
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<data>
|
<data>
|
||||||
<record id="digest_tip_documents_0" model="digest.tip">
|
<record id="digest_tip_documents_0" model="digest.tip">
|
||||||
<field name="name">Tip: Become a paperless company</field>
|
<field name="name">Tip: Become a paperless company</field>
|
||||||
<field name="sequence">300</field>
|
<field name="sequence">300</field>
|
||||||
<field name="group_id" ref="documents.group_documents_user" />
|
<field name="group_id" ref="documents.group_documents_user" />
|
||||||
<field name="tip_description" type="html">
|
<field name="tip_description" type="html">
|
||||||
<div>
|
<div>
|
||||||
<t t-set="record" t-value="object.env['documents.document'].search([('alias_name', '!=', False), ('alias_domain_id', '!=', False)], limit=1)" />
|
<t t-set="record" t-value="object.env['documents.document'].search([('alias_name', '!=', False), ('alias_domain_id', '!=', False)], limit=1)" />
|
||||||
<b class="tip_title">Tip: Become a paperless company</b>
|
<b class="tip_title">Tip: Become a paperless company</b>
|
||||||
<t t-if="record.alias_email">
|
<t t-if="record.alias_email">
|
||||||
<p class="tip_content">An easy way to process incoming mails is to configure your scanner to send PDFs to <t t-out="record.alias_email"/>. Scanned files will appear automatically in your workspace. Then, process your documents in bulk with the split tool: launch user defined actions, request a signature, convert to vendor bills with AI, etc.</p>
|
<p class="tip_content">An easy way to process incoming mails is to configure your scanner to send PDFs to <t t-out="record.alias_email"/>. Scanned files will appear automatically in your workspace. Then, process your documents in bulk with the split tool: launch user defined actions, request a signature, convert to vendor bills with AI, etc.</p>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<p class="tip_content">An easy way to process incoming mails is to configure your scanner to send PDFs to your workspace email. Scanned files will appear automatically in your workspace. Then, process your documents in bulk with the split tool: launch user defined actions, request a signature, convert to vendor bills with AI, etc.</p>
|
<p class="tip_content">An easy way to process incoming mails is to configure your scanner to send PDFs to your workspace email. Scanned files will appear automatically in your workspace. Then, process your documents in bulk with the split tool: launch user defined actions, request a signature, convert to vendor bills with AI, etc.</p>
|
||||||
</t>
|
</t>
|
||||||
<img src="/documents/static/src/img/documents-paperless.png" width="540" class="illustration_border" />
|
<img src="/documents/static/src/img/documents-paperless.png" width="540" class="illustration_border" />
|
||||||
</div>
|
</div>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,43 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo><data noupdate="1">
|
<odoo><data noupdate="1">
|
||||||
|
|
||||||
<!-- Folders -->
|
<!-- Folders -->
|
||||||
<record id="document_internal_folder" model="documents.document" forcecreate="0">
|
<record id="document_internal_folder" model="documents.document" forcecreate="0">
|
||||||
<field name="type">folder</field>
|
<field name="type">folder</field>
|
||||||
<field name="access_internal">view</field>
|
<field name="access_internal">view</field>
|
||||||
<field name="name">Internal</field>
|
<field name="name">Internal</field>
|
||||||
<field name="is_pinned_folder">True</field>
|
<field name="is_pinned_folder">True</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="document_finance_folder" model="documents.document" forcecreate="0">
|
<record id="document_finance_folder" model="documents.document" forcecreate="0">
|
||||||
<field name="type">folder</field>
|
<field name="type">folder</field>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="name">Finance</field>
|
<field name="name">Finance</field>
|
||||||
<field name="is_pinned_folder">True</field>
|
<field name="is_pinned_folder">True</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="document_marketing_folder" model="documents.document" forcecreate="0">
|
<record id="document_marketing_folder" model="documents.document" forcecreate="0">
|
||||||
<field name="type">folder</field>
|
<field name="type">folder</field>
|
||||||
<field name="name">Marketing</field>
|
<field name="name">Marketing</field>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="is_pinned_folder">True</field>
|
<field name="is_pinned_folder">True</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="document_support_folder" model="documents.document" forcecreate="True">
|
<record id="document_support_folder" model="documents.document" forcecreate="True">
|
||||||
<field name="name">Support</field>
|
<field name="name">Support</field>
|
||||||
<field name="type">folder</field>
|
<field name="type">folder</field>
|
||||||
<field name="access_internal">none</field>
|
<field name="access_internal">none</field>
|
||||||
<field name="access_via_link">none</field>
|
<field name="access_via_link">none</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- base data -->
|
<!-- base data -->
|
||||||
<record id="documents_attachment_video_documents" model="documents.document" forcecreate="0">
|
<record id="documents_attachment_video_documents" model="documents.document" forcecreate="0">
|
||||||
<field name="name">Video: Odoo Documents</field>
|
<field name="name">Video: Odoo Documents</field>
|
||||||
<field name="type">url</field>
|
<field name="type">url</field>
|
||||||
<field name="url">https://youtu.be/Ayab6wZ_U1A</field>
|
<field name="url">https://youtu.be/Ayab6wZ_U1A</field>
|
||||||
<field name="folder_id" ref="documents.document_internal_folder"/>
|
<field name="folder_id" ref="documents.document_internal_folder"/>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_presentations'),
|
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_presentations'),
|
||||||
ref('documents.documents_tag_validated')])]"/>
|
ref('documents.documents_tag_validated')])]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</data></odoo>
|
</data></odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,127 +1,127 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo><data noupdate="1">
|
<odoo><data noupdate="1">
|
||||||
<!-- tags internal -->
|
<!-- tags internal -->
|
||||||
<record id="documents_tag_draft" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_draft" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Draft</field>
|
<field name="name">Draft</field>
|
||||||
<field name="sequence">2</field>
|
<field name="sequence">2</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_inbox" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_inbox" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Inbox</field>
|
<field name="name">Inbox</field>
|
||||||
<field name="sequence">4</field>
|
<field name="sequence">4</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_to_validate" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_to_validate" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">To Validate</field>
|
<field name="name">To Validate</field>
|
||||||
<field name="sequence">6</field>
|
<field name="sequence">6</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_validated" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_validated" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Validated</field>
|
<field name="name">Validated</field>
|
||||||
<field name="sequence">8</field>
|
<field name="sequence">8</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_deprecated" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_deprecated" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Deprecated</field>
|
<field name="name">Deprecated</field>
|
||||||
<field name="sequence">10</field>
|
<field name="sequence">10</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_hr" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_hr" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">HR</field>
|
<field name="name">HR</field>
|
||||||
<field name="sequence">9</field>
|
<field name="sequence">9</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_sales" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_sales" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Sales</field>
|
<field name="name">Sales</field>
|
||||||
<field name="sequence">9</field>
|
<field name="sequence">9</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_legal" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_legal" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Legal</field>
|
<field name="name">Legal</field>
|
||||||
<field name="sequence">9</field>
|
<field name="sequence">9</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_other" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_other" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Other</field>
|
<field name="name">Other</field>
|
||||||
<field name="sequence">10</field>
|
<field name="sequence">10</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_presentations" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_presentations" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Presentations</field>
|
<field name="name">Presentations</field>
|
||||||
<field name="sequence">10</field>
|
<field name="sequence">10</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_contracts" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_contracts" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Contracts</field>
|
<field name="name">Contracts</field>
|
||||||
<field name="sequence">10</field>
|
<field name="sequence">10</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_project" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_project" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Project</field>
|
<field name="name">Project</field>
|
||||||
<field name="sequence">10</field>
|
<field name="sequence">10</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_text" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_text" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Text</field>
|
<field name="name">Text</field>
|
||||||
<field name="sequence">10</field>
|
<field name="sequence">10</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- tags finance -->
|
<!-- tags finance -->
|
||||||
|
|
||||||
<record id="documents_tag_bill" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_bill" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Bill</field>
|
<field name="name">Bill</field>
|
||||||
<field name="sequence">4</field>
|
<field name="sequence">4</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_expense" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_expense" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Expense</field>
|
<field name="name">Expense</field>
|
||||||
<field name="sequence">5</field>
|
<field name="sequence">5</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_vat" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_vat" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">VAT</field>
|
<field name="name">VAT</field>
|
||||||
<field name="sequence">6</field>
|
<field name="sequence">6</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_fiscal" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_fiscal" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Fiscal</field>
|
<field name="name">Fiscal</field>
|
||||||
<field name="sequence">7</field>
|
<field name="sequence">7</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_financial" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_financial" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Financial</field>
|
<field name="name">Financial</field>
|
||||||
<field name="sequence">8</field>
|
<field name="sequence">8</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_year_current" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_year_current" model="documents.tag" forcecreate="0">
|
||||||
<field name="name" eval="str(datetime.now().year)"/>
|
<field name="name" eval="str(datetime.now().year)"/>
|
||||||
<field name="sequence">10</field>
|
<field name="sequence">10</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_year_previous" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_year_previous" model="documents.tag" forcecreate="0">
|
||||||
<field name="name" eval="str(datetime.now().year-1)"/>
|
<field name="name" eval="str(datetime.now().year-1)"/>
|
||||||
<field name="sequence">11</field>
|
<field name="sequence">11</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- tags marketing -->
|
<!-- tags marketing -->
|
||||||
|
|
||||||
<record id="documents_tag_ads" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_ads" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Ads</field>
|
<field name="name">Ads</field>
|
||||||
<field name="sequence">12</field>
|
<field name="sequence">12</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_brochures" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_brochures" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Brochures</field>
|
<field name="name">Brochures</field>
|
||||||
<field name="sequence">13</field>
|
<field name="sequence">13</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_images" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_images" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Images</field>
|
<field name="name">Images</field>
|
||||||
<field name="sequence">14</field>
|
<field name="sequence">14</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_videos" model="documents.tag" forcecreate="0">
|
<record id="documents_tag_videos" model="documents.tag" forcecreate="0">
|
||||||
<field name="name">Videos</field>
|
<field name="name">Videos</field>
|
||||||
<field name="sequence">15</field>
|
<field name="sequence">15</field>
|
||||||
</record>
|
</record>
|
||||||
</data></odoo>
|
</data></odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="documents_tour" model="web_tour.tour">
|
<record id="documents_tour" model="web_tour.tour">
|
||||||
<field name="name">documents_tour</field>
|
<field name="name">documents_tour</field>
|
||||||
<field name="sequence">180</field>
|
<field name="sequence">180</field>
|
||||||
<field name="rainbow_man_message"><![CDATA[
|
<field name="rainbow_man_message"><![CDATA[
|
||||||
Wow... 6 documents processed in a few seconds, You're good.<br/>The tour is complete. Try uploading your own documents now.
|
Wow... 6 documents processed in a few seconds, You're good.<br/>The tour is complete. Try uploading your own documents now.
|
||||||
]]></field>
|
]]></field>
|
||||||
</record>
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,106 +1,106 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
<data>
|
<data>
|
||||||
<record id="ir_actions_server_create_activity" model="ir.actions.server" forcecreate="0">
|
<record id="ir_actions_server_create_activity" model="ir.actions.server" forcecreate="0">
|
||||||
<field name="name">Create Activity</field>
|
<field name="name">Create Activity</field>
|
||||||
<field name="model_id" ref="documents.model_documents_document"/>
|
<field name="model_id" ref="documents.model_documents_document"/>
|
||||||
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
||||||
<field name="state">next_activity</field>
|
<field name="state">next_activity</field>
|
||||||
<field name="activity_type_id" ref="documents.mail_documents_activity_data_tv"/>
|
<field name="activity_type_id" ref="documents.mail_documents_activity_data_tv"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="ir_actions_server_remove_activities" model="ir.actions.server" forcecreate="0">
|
<record id="ir_actions_server_remove_activities" model="ir.actions.server" forcecreate="0">
|
||||||
<field name="name">Mark activities as completed</field>
|
<field name="name">Mark activities as completed</field>
|
||||||
<field name="model_id" ref="documents.model_documents_document"/>
|
<field name="model_id" ref="documents.model_documents_document"/>
|
||||||
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
||||||
<field name="state">code</field>
|
<field name="state">code</field>
|
||||||
<field name="code">
|
<field name="code">
|
||||||
for record in records:
|
for record in records:
|
||||||
record.activity_ids.action_feedback(feedback="completed")
|
record.activity_ids.action_feedback(feedback="completed")
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="ir_actions_server_remove_tags" model="ir.actions.server" forcecreate="0">
|
<record id="ir_actions_server_remove_tags" model="ir.actions.server" forcecreate="0">
|
||||||
<field name="name">Remove all tags</field>
|
<field name="name">Remove all tags</field>
|
||||||
<field name="model_id" ref="documents.model_documents_document"/>
|
<field name="model_id" ref="documents.model_documents_document"/>
|
||||||
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
||||||
<field name="state">object_write</field>
|
<field name="state">object_write</field>
|
||||||
<field name="update_m2m_operation">clear</field>
|
<field name="update_m2m_operation">clear</field>
|
||||||
<field name="update_path">tag_ids</field>
|
<field name="update_path">tag_ids</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="ir_actions_server_send_to_finance" model="ir.actions.server" forcecreate="0">
|
<record id="ir_actions_server_send_to_finance" model="ir.actions.server" forcecreate="0">
|
||||||
<field name="name">Send To Finance</field>
|
<field name="name">Send To Finance</field>
|
||||||
<field name="model_id" ref="documents.model_documents_document"/>
|
<field name="model_id" ref="documents.model_documents_document"/>
|
||||||
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
||||||
<field name="state">code</field>
|
<field name="state">code</field>
|
||||||
<field name="code">
|
<field name="code">
|
||||||
target = env.ref('documents.document_finance_folder', raise_if_not_found=False)
|
target = env.ref('documents.document_finance_folder', raise_if_not_found=False)
|
||||||
if target:
|
if target:
|
||||||
permissions = records.mapped('user_permission')
|
permissions = records.mapped('user_permission')
|
||||||
records.action_move_documents(target.id)
|
records.action_move_documents(target.id)
|
||||||
for record, permission in zip(records, permissions):
|
for record, permission in zip(records, permissions):
|
||||||
record.sudo().action_update_access_rights(partners={env.user.partner_id: (permission, None)})
|
record.sudo().action_update_access_rights(partners={env.user.partner_id: (permission, None)})
|
||||||
action = {
|
action = {
|
||||||
'type': 'ir.actions.client',
|
'type': 'ir.actions.client',
|
||||||
'tag': 'display_notification',
|
'tag': 'display_notification',
|
||||||
'params': {
|
'params': {
|
||||||
'message': env._("%(nb_records)s document(s) sent to Finance", nb_records=len(records)),
|
'message': env._("%(nb_records)s document(s) sent to Finance", nb_records=len(records)),
|
||||||
'type': 'success',
|
'type': 'success',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<function model="documents.document" name="action_folder_embed_action" eval="[
|
<function model="documents.document" name="action_folder_embed_action" eval="[
|
||||||
ref('documents.document_internal_folder'),
|
ref('documents.document_internal_folder'),
|
||||||
ref('documents.ir_actions_server_send_to_finance'),
|
ref('documents.ir_actions_server_send_to_finance'),
|
||||||
]"/>
|
]"/>
|
||||||
|
|
||||||
|
|
||||||
<record id="ir_actions_server_tag_remove_inbox" model="ir.actions.server" forcecreate="0">
|
<record id="ir_actions_server_tag_remove_inbox" model="ir.actions.server" forcecreate="0">
|
||||||
<field name="name">Remove Tag Inbox</field>
|
<field name="name">Remove Tag Inbox</field>
|
||||||
<field name="model_id" ref="documents.model_documents_document"/>
|
<field name="model_id" ref="documents.model_documents_document"/>
|
||||||
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
||||||
<field name="update_path">tag_ids</field>
|
<field name="update_path">tag_ids</field>
|
||||||
<field name="usage">ir_actions_server</field>
|
<field name="usage">ir_actions_server</field>
|
||||||
<field name="state">object_write</field>
|
<field name="state">object_write</field>
|
||||||
<field name="update_m2m_operation">remove</field>
|
<field name="update_m2m_operation">remove</field>
|
||||||
<field name="resource_ref" ref="documents.documents_tag_inbox"/>
|
<field name="resource_ref" ref="documents.documents_tag_inbox"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="ir_actions_server_tag_remove_to_validate" model="ir.actions.server" forcecreate="0">
|
<record id="ir_actions_server_tag_remove_to_validate" model="ir.actions.server" forcecreate="0">
|
||||||
<field name="name">Remove Tag To Validate</field>
|
<field name="name">Remove Tag To Validate</field>
|
||||||
<field name="model_id" ref="documents.model_documents_document"/>
|
<field name="model_id" ref="documents.model_documents_document"/>
|
||||||
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
||||||
<field name="update_path">tag_ids</field>
|
<field name="update_path">tag_ids</field>
|
||||||
<field name="usage">ir_actions_server</field>
|
<field name="usage">ir_actions_server</field>
|
||||||
<field name="state">object_write</field>
|
<field name="state">object_write</field>
|
||||||
<field name="update_m2m_operation">remove</field>
|
<field name="update_m2m_operation">remove</field>
|
||||||
<field name="resource_ref" ref="documents.documents_tag_to_validate"/>
|
<field name="resource_ref" ref="documents.documents_tag_to_validate"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="ir_actions_server_tag_add_validated" model="ir.actions.server" forcecreate="0">
|
<record id="ir_actions_server_tag_add_validated" model="ir.actions.server" forcecreate="0">
|
||||||
<field name="name">Add Tag Validated</field>
|
<field name="name">Add Tag Validated</field>
|
||||||
<field name="model_id" ref="documents.model_documents_document"/>
|
<field name="model_id" ref="documents.model_documents_document"/>
|
||||||
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
||||||
<field name="update_path">tag_ids</field>
|
<field name="update_path">tag_ids</field>
|
||||||
<field name="usage">ir_actions_server</field>
|
<field name="usage">ir_actions_server</field>
|
||||||
<field name="state">object_write</field>
|
<field name="state">object_write</field>
|
||||||
<field name="update_m2m_operation">add</field>
|
<field name="update_m2m_operation">add</field>
|
||||||
<field name="resource_ref" ref="documents.documents_tag_validated"/>
|
<field name="resource_ref" ref="documents.documents_tag_validated"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="ir_actions_server_tag_add_bill" model="ir.actions.server" forcecreate="0">
|
<record id="ir_actions_server_tag_add_bill" model="ir.actions.server" forcecreate="0">
|
||||||
<field name="name">Add Tag Bill</field>
|
<field name="name">Add Tag Bill</field>
|
||||||
<field name="model_id" ref="documents.model_documents_document"/>
|
<field name="model_id" ref="documents.model_documents_document"/>
|
||||||
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
|
||||||
<field name="update_path">tag_ids</field>
|
<field name="update_path">tag_ids</field>
|
||||||
<field name="usage">ir_actions_server</field>
|
<field name="usage">ir_actions_server</field>
|
||||||
<field name="state">object_write</field>
|
<field name="state">object_write</field>
|
||||||
<field name="update_m2m_operation">add</field>
|
<field name="update_m2m_operation">add</field>
|
||||||
<field name="resource_ref" ref="documents.documents_tag_bill"/>
|
<field name="resource_ref" ref="documents.documents_tag_bill"/>
|
||||||
</record>
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo><data noupdate="1">
|
<odoo><data noupdate="1">
|
||||||
<record id="ir_config_document_upload_limit" model="ir.config_parameter">
|
<record id="ir_config_document_upload_limit" model="ir.config_parameter">
|
||||||
<field name="key">document.max_fileupload_size</field>
|
<field name="key">document.max_fileupload_size</field>
|
||||||
<field name="value">67000000</field>
|
<field name="value">67000000</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="ir_config_deletion_delay" model="ir.config_parameter">
|
<record id="ir_config_deletion_delay" model="ir.config_parameter">
|
||||||
<field name="key">documents.deletion_delay</field>
|
<field name="key">documents.deletion_delay</field>
|
||||||
<field name="value">30</field>
|
<field name="value">30</field>
|
||||||
</record>
|
</record>
|
||||||
</data></odoo>
|
</data></odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo><data noupdate="1">
|
<odoo><data noupdate="1">
|
||||||
<record id="mail_documents_activity_data_Inbox" model="mail.activity.type">
|
<record id="mail_documents_activity_data_Inbox" model="mail.activity.type">
|
||||||
<field name="name">Inbox</field>
|
<field name="name">Inbox</field>
|
||||||
<field name="res_model">documents.document</field>
|
<field name="res_model">documents.document</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="mail_documents_activity_data_tv" model="mail.activity.type">
|
<record id="mail_documents_activity_data_tv" model="mail.activity.type">
|
||||||
<field name="name">To validate</field>
|
<field name="name">To validate</field>
|
||||||
<field name="res_model">documents.document</field>
|
<field name="res_model">documents.document</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="mail_documents_activity_data_md" model="mail.activity.type">
|
<record id="mail_documents_activity_data_md" model="mail.activity.type">
|
||||||
<field name="name">Requested Document</field>
|
<field name="name">Requested Document</field>
|
||||||
<field name="category">upload_file</field>
|
<field name="category">upload_file</field>
|
||||||
<field name="res_model">documents.document</field>
|
<field name="res_model">documents.document</field>
|
||||||
<field name="mail_template_ids" eval="[(4, ref('documents.mail_template_document_request_reminder'))]"/>
|
<field name="mail_template_ids" eval="[(4, ref('documents.mail_template_document_request_reminder'))]"/>
|
||||||
</record>
|
</record>
|
||||||
</data></odoo>
|
</data></odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,252 +1,252 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<data noupdate="1">
|
<data noupdate="1">
|
||||||
<!--Email template -->
|
<!--Email template -->
|
||||||
<record id="mail_template_document_request" model="mail.template">
|
<record id="mail_template_document_request" model="mail.template">
|
||||||
<field name="name">Document: Document Request</field>
|
<field name="name">Document: Document Request</field>
|
||||||
<field name="model_id" ref="model_documents_document"/>
|
<field name="model_id" ref="model_documents_document"/>
|
||||||
<field name="subject">Document Request {{ object.name != False and ': '+ object.name or '' }}</field>
|
<field name="subject">Document Request {{ object.name != False and ': '+ object.name or '' }}</field>
|
||||||
<field name="email_to" eval="False"/>
|
<field name="email_to" eval="False"/>
|
||||||
<field name="partner_to">{{ object.requestee_partner_id.id or '' }}</field>
|
<field name="partner_to">{{ object.requestee_partner_id.id or '' }}</field>
|
||||||
<field name="description">Sent to partner when requesting a document from them</field>
|
<field name="description">Sent to partner when requesting a document from them</field>
|
||||||
<field name="body_html" type="html">
|
<field name="body_html" type="html">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">
|
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
|
<table border="0" cellpadding="0" cellspacing="0" width="590" style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- HEADER -->
|
<!-- HEADER -->
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="min-width: 590px;">
|
<td align="center" style="min-width: 590px;">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
||||||
<tr><td valign="middle">
|
<tr><td valign="middle">
|
||||||
<span style="font-size: 10px;">
|
<span style="font-size: 10px;">
|
||||||
Document Request: <br/>
|
Document Request: <br/>
|
||||||
<t t-if="object.name">
|
<t t-if="object.name">
|
||||||
<span style="font-size: 20px; font-weight: bold;" t-out="object.name or ''">Inbox Financial</span>
|
<span style="font-size: 20px; font-weight: bold;" t-out="object.name or ''">Inbox Financial</span>
|
||||||
</t>
|
</t>
|
||||||
</span><br/>
|
</span><br/>
|
||||||
</td><td valign="middle" align="right" t-if="not object.create_uid.company_id.uses_default_logo">
|
</td><td valign="middle" align="right" t-if="not object.create_uid.company_id.uses_default_logo">
|
||||||
<img t-attf-src="/logo.png?company={{ object.create_uid.company_id.id }}" style="padding: 0px; margin: 0px; height: auto; width: 80px;" t-att-alt="object.create_uid.company_id.name"/>
|
<img t-attf-src="/logo.png?company={{ object.create_uid.company_id.id }}" style="padding: 0px; margin: 0px; height: auto; width: 80px;" t-att-alt="object.create_uid.company_id.name"/>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td colspan="2" style="text-align:center;">
|
<tr><td colspan="2" style="text-align:center;">
|
||||||
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
|
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="min-width: 590px;">
|
<td align="center" style="min-width: 590px;">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
||||||
<tr><td valign="top" style="font-size: 13px;">
|
<tr><td valign="top" style="font-size: 13px;">
|
||||||
<div>
|
<div>
|
||||||
Hello <t t-out="object.owner_id.name or ''">OdooBot</t>,
|
Hello <t t-out="object.owner_id.name or ''">OdooBot</t>,
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<t t-out="object.create_uid.name or ''">OdooBot</t> (<t t-out="object.create_uid.email or ''">odoobot@example.com</t>) asks you to provide the following document:
|
<t t-out="object.create_uid.name or ''">OdooBot</t> (<t t-out="object.create_uid.email or ''">odoobot@example.com</t>) asks you to provide the following document:
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<center>
|
<center>
|
||||||
<div>
|
<div>
|
||||||
<t t-if="object.name">
|
<t t-if="object.name">
|
||||||
<b t-out="object.name or ''">Inbox Financial</b>
|
<b t-out="object.name or ''">Inbox Financial</b>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<t t-if="object.request_activity_id.note">
|
<t t-if="object.request_activity_id.note">
|
||||||
<i t-out="object.request_activity_id.note or ''">Example of a note.</i>
|
<i t-out="object.request_activity_id.note or ''">Example of a note.</i>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<div style="margin: 16px 0px 16px 0px;">
|
<div style="margin: 16px 0px 16px 0px;">
|
||||||
<a t-att-href="object.access_url"
|
<a t-att-href="object.access_url"
|
||||||
style="background-color: #875A7B; padding: 20px 30px 20px 30px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
|
style="background-color: #875A7B; padding: 20px 30px 20px 30px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
|
||||||
Upload the requested document
|
Upload the requested document
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</center><br/>
|
</center><br/>
|
||||||
Please provide us with the missing document before <t t-out="object.request_activity_id.date_deadline">2021-05-17</t>.
|
Please provide us with the missing document before <t t-out="object.request_activity_id.date_deadline">2021-05-17</t>.
|
||||||
<t t-if="user and user.signature">
|
<t t-if="user and user.signature">
|
||||||
<br/>
|
<br/>
|
||||||
<t t-out="user.signature or ''">--<br/>Mitchell Admin</t>
|
<t t-out="user.signature or ''">--<br/>Mitchell Admin</t>
|
||||||
<br/>
|
<br/>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td style="text-align:center;">
|
<tr><td style="text-align:center;">
|
||||||
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
|
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- FOOTER -->
|
<!-- FOOTER -->
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="min-width: 590px;">
|
<td align="center" style="min-width: 590px;">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
||||||
<tr><td valign="middle" align="left">
|
<tr><td valign="middle" align="left">
|
||||||
<t t-out="object.create_uid.company_id.name or ''">YourCompany</t>
|
<t t-out="object.create_uid.company_id.name or ''">YourCompany</t>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td valign="middle" align="left" style="opacity: 0.7;">
|
<tr><td valign="middle" align="left" style="opacity: 0.7;">
|
||||||
<t t-out="object.create_uid.company_id.phone or ''">+1 650-123-4567</t>
|
<t t-out="object.create_uid.company_id.phone or ''">+1 650-123-4567</t>
|
||||||
<t t-if="object.create_uid.company_id.email">
|
<t t-if="object.create_uid.company_id.email">
|
||||||
| <a t-attf-href="'mailto:%s' % {{ object.create_uid.company_id.email }}" style="text-decoration:none; color: #454748;" t-out="object.create_uid.company_id.email or ''">info@yourcompany.com</a>
|
| <a t-attf-href="'mailto:%s' % {{ object.create_uid.company_id.email }}" style="text-decoration:none; color: #454748;" t-out="object.create_uid.company_id.email or ''">info@yourcompany.com</a>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="object.create_uid.company_id.website">
|
<t t-if="object.create_uid.company_id.website">
|
||||||
| <a t-attf-href="'%s' % {{ object.create_uid.company_id.website }}" style="text-decoration:none; color: #454748;" t-out="object.create_uid.company_id.website or ''">http://www.example.com</a>
|
| <a t-attf-href="'%s' % {{ object.create_uid.company_id.website }}" style="text-decoration:none; color: #454748;" t-out="object.create_uid.company_id.website or ''">http://www.example.com</a>
|
||||||
</t>
|
</t>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<!-- POWERED BY -->
|
<!-- POWERED BY -->
|
||||||
<tr><td align="center" style="min-width: 590px;">
|
<tr><td align="center" style="min-width: 590px;">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: #F1F1F1; color: #454748; padding: 8px; border-collapse:separate;">
|
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: #F1F1F1; color: #454748; padding: 8px; border-collapse:separate;">
|
||||||
<tr><td style="text-align: center; font-size: 13px;">
|
<tr><td style="text-align: center; font-size: 13px;">
|
||||||
Powered by <a target="_blank" href="https://www.ftprotech.in/app/documents" style="color: #875A7B;">Odoo Documents</a>
|
Powered by <a target="_blank" href="https://www.ftprotech.in/app/documents" style="color: #875A7B;">Odoo Documents</a>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</field>
|
</field>
|
||||||
<field name="lang">{{ object.owner_id.lang }}</field>
|
<field name="lang">{{ object.owner_id.lang }}</field>
|
||||||
<field name="auto_delete" eval="True"/>
|
<field name="auto_delete" eval="True"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<template id="mail_template_document_share">
|
<template id="mail_template_document_share">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: white; padding: 0; border-collapse:separate; margin-bottom:13px;">
|
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: white; padding: 0; border-collapse:separate; margin-bottom:13px;">
|
||||||
<tr><td valign="top">
|
<tr><td valign="top">
|
||||||
<div style="margin: 0px; padding: 0px; font-size: 13px;">
|
<div style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||||
<t t-if="record.name">
|
<t t-if="record.name">
|
||||||
<t t-if="record.type == 'folder'">
|
<t t-if="record.type == 'folder'">
|
||||||
<t t-out="user.name or ''"/> shared this folder with you: <t t-out="record.name"/>.<br/>
|
<t t-out="user.name or ''"/> shared this folder with you: <t t-out="record.name"/>.<br/>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<t t-out="user.name or ''"/> shared this document with you: <t t-out="record.name"/>.<br/>
|
<t t-out="user.name or ''"/> shared this document with you: <t t-out="record.name"/>.<br/>
|
||||||
</t>
|
</t>
|
||||||
</t>
|
</t>
|
||||||
<t t-elif="record.type == 'folder'">
|
<t t-elif="record.type == 'folder'">
|
||||||
<t t-out="user.name or ''"/> shared a folder with you.<br/>
|
<t t-out="user.name or ''"/> shared a folder with you.<br/>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<t t-out="user.name or ''"/> shared a document with you.<br/>
|
<t t-out="user.name or ''"/> shared a document with you.<br/>
|
||||||
</t>
|
</t>
|
||||||
<div t-if="message" style="color:#777; margin-top:13px;" t-out="message"/>
|
<div t-if="message" style="color:#777; margin-top:13px;" t-out="message"/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Manual reminder; copy of document request template -->
|
<!-- Manual reminder; copy of document request template -->
|
||||||
<record id="mail_template_document_request_reminder" model="mail.template">
|
<record id="mail_template_document_request_reminder" model="mail.template">
|
||||||
<field name="name">Document Request: Reminder</field>
|
<field name="name">Document Request: Reminder</field>
|
||||||
<field name="model_id" ref="model_documents_document"/>
|
<field name="model_id" ref="model_documents_document"/>
|
||||||
<field name="subject">Reminder to upload your document{{ object.name and ' : ' + object.name or '' }}</field>
|
<field name="subject">Reminder to upload your document{{ object.name and ' : ' + object.name or '' }}</field>
|
||||||
<field name="email_to" eval="False"/>
|
<field name="email_to" eval="False"/>
|
||||||
<field name="partner_to">{{ object.requestee_partner_id.id or '' }}</field>
|
<field name="partner_to">{{ object.requestee_partner_id.id or '' }}</field>
|
||||||
<field name="description">Set reminders in activities to notify users who didn't upload their requested document</field>
|
<field name="description">Set reminders in activities to notify users who didn't upload their requested document</field>
|
||||||
<field name="body_html" type="html">
|
<field name="body_html" type="html">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">
|
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
|
<table border="0" cellpadding="0" cellspacing="0" width="590" style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- HEADER -->
|
<!-- HEADER -->
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="min-width: 590px;">
|
<td align="center" style="min-width: 590px;">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
||||||
<tr><td valign="middle">
|
<tr><td valign="middle">
|
||||||
<span style="font-size: 10px;">
|
<span style="font-size: 10px;">
|
||||||
Document Request: <br/>
|
Document Request: <br/>
|
||||||
<t t-if="object.name">
|
<t t-if="object.name">
|
||||||
<span style="font-size: 20px; font-weight: bold;" t-out="object.name or ''">Inbox Financial</span>
|
<span style="font-size: 20px; font-weight: bold;" t-out="object.name or ''">Inbox Financial</span>
|
||||||
</t>
|
</t>
|
||||||
</span><br/>
|
</span><br/>
|
||||||
</td><td valign="middle" align="right">
|
</td><td valign="middle" align="right">
|
||||||
<img t-attf-src="/logo.png?company={{ object.create_uid.company_id.id }}" style="padding: 0px; margin: 0px; height: auto; width: 80px;" t-att-alt="object.create_uid.company_id.name"/>
|
<img t-attf-src="/logo.png?company={{ object.create_uid.company_id.id }}" style="padding: 0px; margin: 0px; height: auto; width: 80px;" t-att-alt="object.create_uid.company_id.name"/>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td colspan="2" style="text-align:center;">
|
<tr><td colspan="2" style="text-align:center;">
|
||||||
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
|
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="min-width: 590px;">
|
<td align="center" style="min-width: 590px;">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
||||||
<tr><td valign="top" style="font-size: 13px;">
|
<tr><td valign="top" style="font-size: 13px;">
|
||||||
<div>
|
<div>
|
||||||
Hello <t t-out="object.owner_id.name or ''">OdooBot</t>,
|
Hello <t t-out="object.owner_id.name or ''">OdooBot</t>,
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
This is a friendly reminder to upload your requested document:
|
This is a friendly reminder to upload your requested document:
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<center>
|
<center>
|
||||||
<div>
|
<div>
|
||||||
<t t-if="object.name">
|
<t t-if="object.name">
|
||||||
<b t-out="object.name or ''">Inbox Financial</b>
|
<b t-out="object.name or ''">Inbox Financial</b>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<t t-if="object.request_activity_id.note">
|
<t t-if="object.request_activity_id.note">
|
||||||
<i t-out="object.request_activity_id.note or ''">Example of a note.</i>
|
<i t-out="object.request_activity_id.note or ''">Example of a note.</i>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<div style="margin: 16px 0px 16px 0px;">
|
<div style="margin: 16px 0px 16px 0px;">
|
||||||
<a t-att-href="object.access_url"
|
<a t-att-href="object.access_url"
|
||||||
style="background-color: #875A7B; padding: 20px 30px 20px 30px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
|
style="background-color: #875A7B; padding: 20px 30px 20px 30px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
|
||||||
Upload the requested document
|
Upload the requested document
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</center><br/>
|
</center><br/>
|
||||||
Please provide us with the missing document before <t t-out="object.request_activity_id.date_deadline or ''">2021-05-17</t>.
|
Please provide us with the missing document before <t t-out="object.request_activity_id.date_deadline or ''">2021-05-17</t>.
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
Thank you,
|
Thank you,
|
||||||
<t t-if="user and user.signature">
|
<t t-if="user and user.signature">
|
||||||
<br/>
|
<br/>
|
||||||
<t t-out="user.signature">--<br/>Mitchell Admin</t>
|
<t t-out="user.signature">--<br/>Mitchell Admin</t>
|
||||||
<br/>
|
<br/>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td style="text-align:center;">
|
<tr><td style="text-align:center;">
|
||||||
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
|
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- FOOTER -->
|
<!-- FOOTER -->
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="min-width: 590px;">
|
<td align="center" style="min-width: 590px;">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">
|
||||||
<tr><td valign="middle" align="left">
|
<tr><td valign="middle" align="left">
|
||||||
<t t-out="object.create_uid.company_id.name or ''">YourCompany</t>
|
<t t-out="object.create_uid.company_id.name or ''">YourCompany</t>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td valign="middle" align="left" style="opacity: 0.7;">
|
<tr><td valign="middle" align="left" style="opacity: 0.7;">
|
||||||
<t t-out="object.create_uid.company_id.phone or ''">+1 650-123-4567</t>
|
<t t-out="object.create_uid.company_id.phone or ''">+1 650-123-4567</t>
|
||||||
<t t-if="object.create_uid.company_id.email">
|
<t t-if="object.create_uid.company_id.email">
|
||||||
| <a t-attf-href="'mailto:%s' % {{ object.create_uid.company_id.email }}" style="text-decoration:none; color: #454748;" t-out="object.create_uid.company_id.email">info@yourcompany.com</a>
|
| <a t-attf-href="'mailto:%s' % {{ object.create_uid.company_id.email }}" style="text-decoration:none; color: #454748;" t-out="object.create_uid.company_id.email">info@yourcompany.com</a>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="object.create_uid.company_id.website">
|
<t t-if="object.create_uid.company_id.website">
|
||||||
| <a t-att-href="object.create_uid.company_id.website" style="text-decoration:none; color: #454748;" t-out="object.create_uid.company_id.website">http://www.example.com</a>
|
| <a t-att-href="object.create_uid.company_id.website" style="text-decoration:none; color: #454748;" t-out="object.create_uid.company_id.website">http://www.example.com</a>
|
||||||
</t>
|
</t>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<!-- POWERED BY -->
|
<!-- POWERED BY -->
|
||||||
<tr><td align="center" style="min-width: 590px;">
|
<tr><td align="center" style="min-width: 590px;">
|
||||||
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: #F1F1F1; color: #454748; padding: 8px; border-collapse:separate;">
|
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: #F1F1F1; color: #454748; padding: 8px; border-collapse:separate;">
|
||||||
<tr><td style="text-align: center; font-size: 13px;">
|
<tr><td style="text-align: center; font-size: 13px;">
|
||||||
Powered by <a target="_blank" href="https://www.ftprotech.in/app/documents" style="color: #875A7B;">Odoo Documents</a>
|
Powered by <a target="_blank" href="https://www.ftprotech.in/app/documents" style="color: #875A7B;">Odoo Documents</a>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1,122 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<data noupdate="1">
|
<data noupdate="1">
|
||||||
<record id="base.user_demo" model="res.users">
|
<record id="base.user_demo" model="res.users">
|
||||||
<field name="groups_id" eval="[(3, ref('documents.group_documents_manager'))]"/>
|
<field name="groups_id" eval="[(3, ref('documents.group_documents_manager'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- folders -->
|
<!-- folders -->
|
||||||
<record id="document_marketing_brand1_folder" model="documents.document" forcecreate="0">
|
<record id="document_marketing_brand1_folder" model="documents.document" forcecreate="0">
|
||||||
<field name="type">folder</field>
|
<field name="type">folder</field>
|
||||||
<field name="folder_id" ref="document_marketing_folder"/>
|
<field name="folder_id" ref="document_marketing_folder"/>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="name">Brand 1</field>
|
<field name="name">Brand 1</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="document_marketing_brand1_shared_folder" model="documents.document" forcecreate="0">
|
<record id="document_marketing_brand1_shared_folder" model="documents.document" forcecreate="0">
|
||||||
<field name="type">folder</field>
|
<field name="type">folder</field>
|
||||||
<field name="folder_id" ref="document_marketing_brand1_folder"/>
|
<field name="folder_id" ref="document_marketing_brand1_folder"/>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="access_via_link">view</field>
|
<field name="access_via_link">view</field>
|
||||||
<field name="name">Shared</field>
|
<field name="name">Shared</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="document_marketing_brand2_folder" model="documents.document" forcecreate="0">
|
<record id="document_marketing_brand2_folder" model="documents.document" forcecreate="0">
|
||||||
<field name="type">folder</field>
|
<field name="type">folder</field>
|
||||||
<field name="folder_id" ref="document_marketing_folder"/>
|
<field name="folder_id" ref="document_marketing_folder"/>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="name">Brand 2</field>
|
<field name="name">Brand 2</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- internal -->
|
<!-- internal -->
|
||||||
|
|
||||||
<record id="documents_data_multi_pdf_document" model="documents.document" forcecreate="0">
|
<record id="documents_data_multi_pdf_document" model="documents.document" forcecreate="0">
|
||||||
<field name="name">Mails_inbox.pdf</field>
|
<field name="name">Mails_inbox.pdf</field>
|
||||||
<field name="datas" type="base64" file="documents/data/files/Mails_inbox.pdf"/>
|
<field name="datas" type="base64" file="documents/data/files/Mails_inbox.pdf"/>
|
||||||
<field name="folder_id" ref="documents.document_internal_folder"/>
|
<field name="folder_id" ref="documents.document_internal_folder"/>
|
||||||
<field name="access_internal">view</field>
|
<field name="access_internal">view</field>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_inbox')])]"/>
|
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_inbox')])]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_image_city_document" model="documents.document" forcecreate="0">
|
<record id="documents_image_city_document" model="documents.document" forcecreate="0">
|
||||||
<field name="name">city.jpg</field>
|
<field name="name">city.jpg</field>
|
||||||
<field name="datas" type="base64" file="documents/demo/files/city.jpg"/>
|
<field name="datas" type="base64" file="documents/demo/files/city.jpg"/>
|
||||||
<field name="folder_id" ref="documents.document_internal_folder"/>
|
<field name="folder_id" ref="documents.document_internal_folder"/>
|
||||||
<field name="access_internal">view</field>
|
<field name="access_internal">view</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_image_mail_document" model="documents.document" forcecreate="0">
|
<record id="documents_image_mail_document" model="documents.document" forcecreate="0">
|
||||||
<field name="name">mail.png</field>
|
<field name="name">mail.png</field>
|
||||||
<field name="datas" type="base64" file="documents/data/files/mail.png"/>
|
<field name="datas" type="base64" file="documents/data/files/mail.png"/>
|
||||||
<field name="folder_id" ref="documents.document_internal_folder"/>
|
<field name="folder_id" ref="documents.document_internal_folder"/>
|
||||||
<field name="access_internal">view</field>
|
<field name="access_internal">view</field>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_inbox')])]"/>
|
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_inbox')])]"/>
|
||||||
</record>
|
</record>
|
||||||
<!-- The thumbnail is added after -->
|
<!-- The thumbnail is added after -->
|
||||||
<record id="documents_image_mail_document" model="documents.document" forcecreate="0">
|
<record id="documents_image_mail_document" model="documents.document" forcecreate="0">
|
||||||
<field name="thumbnail" type="base64" file="documents/data/files/mail_thumbnail.png"/>
|
<field name="thumbnail" type="base64" file="documents/data/files/mail_thumbnail.png"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_image_people_document" model="documents.document" forcecreate="0">
|
<record id="documents_image_people_document" model="documents.document" forcecreate="0">
|
||||||
<field name="name">people.jpg</field>
|
<field name="name">people.jpg</field>
|
||||||
<field name="datas" type="base64" file="documents/demo/files/people.jpg"/>
|
<field name="datas" type="base64" file="documents/demo/files/people.jpg"/>
|
||||||
<field name="folder_id" ref="documents.document_internal_folder"/>
|
<field name="folder_id" ref="documents.document_internal_folder"/>
|
||||||
<field name="access_internal">view</field>
|
<field name="access_internal">view</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
<!-- finance -->
|
<!-- finance -->
|
||||||
|
|
||||||
<record id="documents_vendor_bill_inv_007" model="documents.document" forcecreate="0">
|
<record id="documents_vendor_bill_inv_007" model="documents.document" forcecreate="0">
|
||||||
<field name="name">Invoice-INV_2018_0007.pdf</field>
|
<field name="name">Invoice-INV_2018_0007.pdf</field>
|
||||||
<field name="datas" type="base64" file="documents/demo/files/Invoice2018_0007.pdf"/>
|
<field name="datas" type="base64" file="documents/demo/files/Invoice2018_0007.pdf"/>
|
||||||
<field name="folder_id" ref="documents.document_finance_folder"/>
|
<field name="folder_id" ref="documents.document_finance_folder"/>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_validated')])]"/>
|
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_validated')])]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_vendor_bill_extract_azure_interior_document" model="documents.document" forcecreate="0">
|
<record id="documents_vendor_bill_extract_azure_interior_document" model="documents.document" forcecreate="0">
|
||||||
<field name="name">invoice Azure Interior.pdf</field>
|
<field name="name">invoice Azure Interior.pdf</field>
|
||||||
<field name="datas" type="base64" file="documents/demo/files/invoice_azure_interior.pdf"/>
|
<field name="datas" type="base64" file="documents/demo/files/invoice_azure_interior.pdf"/>
|
||||||
<field name="folder_id" ref="documents.document_finance_folder"/>
|
<field name="folder_id" ref="documents.document_finance_folder"/>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_to_validate')])]"/>
|
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_to_validate')])]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_vendor_bill_extract_open_value_document" model="documents.document" forcecreate="0">
|
<record id="documents_vendor_bill_extract_open_value_document" model="documents.document" forcecreate="0">
|
||||||
<field name="name">invoice OpenValue.pdf</field>
|
<field name="name">invoice OpenValue.pdf</field>
|
||||||
<field name="datas" type="base64" file="documents/demo/files/invoice_openvalue.pdf"/>
|
<field name="datas" type="base64" file="documents/demo/files/invoice_openvalue.pdf"/>
|
||||||
<field name="folder_id" ref="documents.document_finance_folder"/>
|
<field name="folder_id" ref="documents.document_finance_folder"/>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_inbox')])]"/>
|
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_inbox')])]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_data_comercial_tenancy_agreement" model="documents.document" forcecreate="0">
|
<record id="documents_data_comercial_tenancy_agreement" model="documents.document" forcecreate="0">
|
||||||
<field name="name">Commercial-Tenancy-Agreement.pdf</field>
|
<field name="name">Commercial-Tenancy-Agreement.pdf</field>
|
||||||
<field name="datas" type="base64" file="documents/demo/files/Commercial-Tenancy-Agreement.pdf"/>
|
<field name="datas" type="base64" file="documents/demo/files/Commercial-Tenancy-Agreement.pdf"/>
|
||||||
<field name="folder_id" ref="documents.document_finance_folder"/>
|
<field name="folder_id" ref="documents.document_finance_folder"/>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_inbox')])]"/>
|
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_inbox')])]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- marketing -->
|
<!-- marketing -->
|
||||||
|
|
||||||
<record id="documents_image_La_landscape_document" model="documents.document" forcecreate="0">
|
<record id="documents_image_La_landscape_document" model="documents.document" forcecreate="0">
|
||||||
<field name="name">LA landscape.jpg</field>
|
<field name="name">LA landscape.jpg</field>
|
||||||
<field name="datas" type="base64" file="documents/demo/files/la.jpg"/>
|
<field name="datas" type="base64" file="documents/demo/files/la.jpg"/>
|
||||||
<field name="folder_id" ref="documents.document_marketing_brand1_folder"/>
|
<field name="folder_id" ref="documents.document_marketing_brand1_folder"/>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_images')])]"/>
|
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_images')])]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_attachment_sorry_netsuite_document" model="documents.document" forcecreate="0">
|
<record id="documents_attachment_sorry_netsuite_document" model="documents.document" forcecreate="0">
|
||||||
<field name="name">Sorry Netsuite.jpg</field>
|
<field name="name">Sorry Netsuite.jpg</field>
|
||||||
<field name="datas" type="base64" file="documents/demo/files/sorry_netsuite.jpg"/>
|
<field name="datas" type="base64" file="documents/demo/files/sorry_netsuite.jpg"/>
|
||||||
<field name="folder_id" ref="documents.document_marketing_brand1_shared_folder"/>
|
<field name="folder_id" ref="documents.document_marketing_brand1_shared_folder"/>
|
||||||
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_ads')])]"/>
|
<field name="tag_ids" eval="[(6,0,[ref('documents.documents_tag_ads')])]"/>
|
||||||
<field name="access_internal">edit</field>
|
<field name="access_internal">edit</field>
|
||||||
<field name="access_via_link">view</field>
|
<field name="access_via_link">view</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,23 +1,23 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
# mixin
|
# mixin
|
||||||
from . import documents_unlink_mixin
|
from . import documents_unlink_mixin
|
||||||
from . import documents_mixin
|
from . import documents_mixin
|
||||||
|
|
||||||
# documents
|
# documents
|
||||||
from . import documents_access
|
from . import documents_access
|
||||||
from . import documents_document
|
from . import documents_document
|
||||||
from . import documents_redirect
|
from . import documents_redirect
|
||||||
from . import documents_tag
|
from . import documents_tag
|
||||||
|
|
||||||
# orm
|
# orm
|
||||||
from . import ir_attachment
|
from . import ir_attachment
|
||||||
from . import ir_binary
|
from . import ir_binary
|
||||||
|
|
||||||
# inherit
|
# inherit
|
||||||
from . import mail_activity
|
from . import mail_activity
|
||||||
from . import mail_activity_type
|
from . import mail_activity_type
|
||||||
from . import res_partner
|
from . import res_partner
|
||||||
from . import res_users
|
from . import res_users
|
||||||
from . import res_config_settings
|
from . import res_config_settings
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,41 @@
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import AccessError
|
from odoo.exceptions import AccessError
|
||||||
|
|
||||||
|
|
||||||
class DocumentAccess(models.Model):
|
class DocumentAccess(models.Model):
|
||||||
_name = 'documents.access'
|
_name = 'documents.access'
|
||||||
_description = 'Document / Partner'
|
_description = 'Document / Partner'
|
||||||
_log_access = False
|
_log_access = False
|
||||||
|
|
||||||
document_id = fields.Many2one('documents.document', required=True, ondelete='cascade')
|
document_id = fields.Many2one('documents.document', required=True, ondelete='cascade')
|
||||||
partner_id = fields.Many2one('res.partner', required=True, ondelete='cascade', index=True)
|
partner_id = fields.Many2one('res.partner', required=True, ondelete='cascade', index=True)
|
||||||
role = fields.Selection(
|
role = fields.Selection(
|
||||||
[('view', 'Viewer'), ('edit', 'Editor')],
|
[('view', 'Viewer'), ('edit', 'Editor')],
|
||||||
string='Role', required=False, index=True)
|
string='Role', required=False, index=True)
|
||||||
last_access_date = fields.Datetime('Last Accessed On', required=False)
|
last_access_date = fields.Datetime('Last Accessed On', required=False)
|
||||||
expiration_date = fields.Datetime('Expiration', index=True)
|
expiration_date = fields.Datetime('Expiration', index=True)
|
||||||
|
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
('unique_document_access_partner', 'unique(document_id, partner_id)',
|
('unique_document_access_partner', 'unique(document_id, partner_id)',
|
||||||
'This partner is already set on this document.'),
|
'This partner is already set on this document.'),
|
||||||
('role_or_last_access_date', 'check (role IS NOT NULL or last_access_date IS NOT NULL)',
|
('role_or_last_access_date', 'check (role IS NOT NULL or last_access_date IS NOT NULL)',
|
||||||
'NULL roles must have a set last_access_date'),
|
'NULL roles must have a set last_access_date'),
|
||||||
]
|
]
|
||||||
|
|
||||||
def _prepare_create_values(self, vals_list):
|
def _prepare_create_values(self, vals_list):
|
||||||
vals_list = super()._prepare_create_values(vals_list)
|
vals_list = super()._prepare_create_values(vals_list)
|
||||||
documents = self.env['documents.document'].browse(
|
documents = self.env['documents.document'].browse(
|
||||||
[vals['document_id'] for vals in vals_list])
|
[vals['document_id'] for vals in vals_list])
|
||||||
documents.check_access('write')
|
documents.check_access('write')
|
||||||
return vals_list
|
return vals_list
|
||||||
|
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
if 'partner_id' in vals or 'document_id' in vals:
|
if 'partner_id' in vals or 'document_id' in vals:
|
||||||
raise AccessError(_('Access documents and partners cannot be changed.'))
|
raise AccessError(_('Access documents and partners cannot be changed.'))
|
||||||
|
|
||||||
self.document_id.check_access('write')
|
self.document_id.check_access('write')
|
||||||
return super().write(vals)
|
return super().write(vals)
|
||||||
|
|
||||||
@api.autovacuum
|
@api.autovacuum
|
||||||
def _gc_expired(self):
|
def _gc_expired(self):
|
||||||
self.search([('expiration_date', '<=', fields.Datetime.now())], limit=1000).unlink()
|
self.search([('expiration_date', '<=', fields.Datetime.now())], limit=1000).unlink()
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,143 +1,143 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo import Command, models
|
from odoo import Command, models
|
||||||
|
|
||||||
|
|
||||||
class DocumentMixin(models.AbstractModel):
|
class DocumentMixin(models.AbstractModel):
|
||||||
"""
|
"""
|
||||||
Inherit this mixin to automatically create a `documents.document` when
|
Inherit this mixin to automatically create a `documents.document` when
|
||||||
an `ir.attachment` is linked to a record and add the default values when
|
an `ir.attachment` is linked to a record and add the default values when
|
||||||
creating a document related to the model that inherits from this mixin.
|
creating a document related to the model that inherits from this mixin.
|
||||||
|
|
||||||
Override this mixin's methods to specify an owner, a folder, tags or
|
Override this mixin's methods to specify an owner, a folder, tags or
|
||||||
access_rights for the document.
|
access_rights for the document.
|
||||||
|
|
||||||
Note: this mixin can be disabled with the context variable "no_document=True".
|
Note: this mixin can be disabled with the context variable "no_document=True".
|
||||||
"""
|
"""
|
||||||
_name = 'documents.mixin'
|
_name = 'documents.mixin'
|
||||||
_inherit = 'documents.unlink.mixin'
|
_inherit = 'documents.unlink.mixin'
|
||||||
_description = "Documents creation mixin"
|
_description = "Documents creation mixin"
|
||||||
|
|
||||||
def _get_document_vals(self, attachment):
|
def _get_document_vals(self, attachment):
|
||||||
"""
|
"""
|
||||||
Return values used to create a `documents.document`
|
Return values used to create a `documents.document`
|
||||||
"""
|
"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
document_vals = {}
|
document_vals = {}
|
||||||
if self._check_create_documents():
|
if self._check_create_documents():
|
||||||
access_rights_vals = self._get_document_vals_access_rights()
|
access_rights_vals = self._get_document_vals_access_rights()
|
||||||
if set(access_rights_vals) - {'access_via_link', 'access_internal', 'is_access_via_link_hidden'}:
|
if set(access_rights_vals) - {'access_via_link', 'access_internal', 'is_access_via_link_hidden'}:
|
||||||
raise ValueError("Invalid access right values")
|
raise ValueError("Invalid access right values")
|
||||||
document_vals = {
|
document_vals = {
|
||||||
'attachment_id': attachment.id,
|
'attachment_id': attachment.id,
|
||||||
'name': attachment.name or self.display_name,
|
'name': attachment.name or self.display_name,
|
||||||
'folder_id': self._get_document_folder().id,
|
'folder_id': self._get_document_folder().id,
|
||||||
'owner_id': self._get_document_owner().id,
|
'owner_id': self._get_document_owner().id,
|
||||||
'partner_id': self._get_document_partner().id,
|
'partner_id': self._get_document_partner().id,
|
||||||
'tag_ids': [(6, 0, self._get_document_tags().ids)],
|
'tag_ids': [(6, 0, self._get_document_tags().ids)],
|
||||||
} | access_rights_vals
|
} | access_rights_vals
|
||||||
return document_vals
|
return document_vals
|
||||||
|
|
||||||
def _get_document_vals_access_rights(self):
|
def _get_document_vals_access_rights(self):
|
||||||
""" Return access rights values to create a `documents.document`
|
""" Return access rights values to create a `documents.document`
|
||||||
|
|
||||||
In the default implementation, we give the minimal permission and rely on the propagation of the folder
|
In the default implementation, we give the minimal permission and rely on the propagation of the folder
|
||||||
permission but this method can be overridden to set more open rights.
|
permission but this method can be overridden to set more open rights.
|
||||||
|
|
||||||
Authorized fields: access_via_link, access_internal, is_access_via_link_hidden.
|
Authorized fields: access_via_link, access_internal, is_access_via_link_hidden.
|
||||||
Note: access_ids are handled differently because when set, it prevents inheritance from the parent folder
|
Note: access_ids are handled differently because when set, it prevents inheritance from the parent folder
|
||||||
(see specific document override).
|
(see specific document override).
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
'access_via_link': 'none',
|
'access_via_link': 'none',
|
||||||
'access_internal': 'none',
|
'access_internal': 'none',
|
||||||
'is_access_via_link_hidden': True,
|
'is_access_via_link_hidden': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_document_owner(self):
|
def _get_document_owner(self):
|
||||||
""" Return the owner value to create a `documents.document`
|
""" Return the owner value to create a `documents.document`
|
||||||
|
|
||||||
In the default implementation, we return OdooBot as owner to avoid giving full access to a user and to rely
|
In the default implementation, we return OdooBot as owner to avoid giving full access to a user and to rely
|
||||||
instead on explicit access managed via `document.access` or via parent folder access inheritance but this
|
instead on explicit access managed via `document.access` or via parent folder access inheritance but this
|
||||||
method can be overridden to for example give the ownership to the current user.
|
method can be overridden to for example give the ownership to the current user.
|
||||||
"""
|
"""
|
||||||
return self.env.ref('base.user_root')
|
return self.env.ref('base.user_root')
|
||||||
|
|
||||||
def _get_document_tags(self):
|
def _get_document_tags(self):
|
||||||
return self.env['documents.tag']
|
return self.env['documents.tag']
|
||||||
|
|
||||||
def _get_document_folder(self):
|
def _get_document_folder(self):
|
||||||
return self.env['documents.document']
|
return self.env['documents.document']
|
||||||
|
|
||||||
def _get_document_partner(self):
|
def _get_document_partner(self):
|
||||||
return self.env['res.partner']
|
return self.env['res.partner']
|
||||||
|
|
||||||
def _get_document_access_ids(self):
|
def _get_document_access_ids(self):
|
||||||
""" Add or remove members
|
""" Add or remove members
|
||||||
|
|
||||||
:return boolean|list: list of tuple (partner, (role, expiration_date)) or False to avoid
|
:return boolean|list: list of tuple (partner, (role, expiration_date)) or False to avoid
|
||||||
inheriting members from parent folder.
|
inheriting members from parent folder.
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _check_create_documents(self):
|
def _check_create_documents(self):
|
||||||
return bool(self and self._get_document_folder())
|
return bool(self and self._get_document_folder())
|
||||||
|
|
||||||
def _prepare_document_create_values_for_linked_records(
|
def _prepare_document_create_values_for_linked_records(
|
||||||
self, res_model, vals_list, pre_vals_list):
|
self, res_model, vals_list, pre_vals_list):
|
||||||
""" Set default value defined on the document mixin implementation of the related record if there are not
|
""" Set default value defined on the document mixin implementation of the related record if there are not
|
||||||
explicitly set.
|
explicitly set.
|
||||||
|
|
||||||
:param str res_model: model referenced by the documents to consider
|
:param str res_model: model referenced by the documents to consider
|
||||||
:param list[dict] vals_list: list of values
|
:param list[dict] vals_list: list of values
|
||||||
:param list[dict] pre_vals_list: list of values before _prepare_create_values (no permission inherited yet)
|
:param list[dict] pre_vals_list: list of values before _prepare_create_values (no permission inherited yet)
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
- This method doesn't override existing values (permission, owner, ...).
|
- This method doesn't override existing values (permission, owner, ...).
|
||||||
- The related record res_model must inherit from DocumentMixin
|
- The related record res_model must inherit from DocumentMixin
|
||||||
"""
|
"""
|
||||||
if self._name != res_model:
|
if self._name != res_model:
|
||||||
raise ValueError(f'Invalid model {res_model} (expected {self._name})')
|
raise ValueError(f'Invalid model {res_model} (expected {self._name})')
|
||||||
|
|
||||||
related_record_by_id = self.env[res_model].browse([
|
related_record_by_id = self.env[res_model].browse([
|
||||||
res_id for vals in vals_list if (res_id := vals.get('res_id'))]).grouped('id')
|
res_id for vals in vals_list if (res_id := vals.get('res_id'))]).grouped('id')
|
||||||
for vals, pre_vals in zip(vals_list, pre_vals_list):
|
for vals, pre_vals in zip(vals_list, pre_vals_list):
|
||||||
if not vals.get('res_id'):
|
if not vals.get('res_id'):
|
||||||
continue
|
continue
|
||||||
related_record = related_record_by_id.get(vals['res_id'])
|
related_record = related_record_by_id.get(vals['res_id'])
|
||||||
vals.update(
|
vals.update(
|
||||||
{
|
{
|
||||||
'owner_id': pre_vals.get('owner_id', related_record._get_document_owner().id),
|
'owner_id': pre_vals.get('owner_id', related_record._get_document_owner().id),
|
||||||
'partner_id': pre_vals.get('partner_id', related_record._get_document_partner().id),
|
'partner_id': pre_vals.get('partner_id', related_record._get_document_partner().id),
|
||||||
'tag_ids': pre_vals.get('tag_ids', [(6, 0, related_record._get_document_tags().ids)]),
|
'tag_ids': pre_vals.get('tag_ids', [(6, 0, related_record._get_document_tags().ids)]),
|
||||||
} | {
|
} | {
|
||||||
key: value
|
key: value
|
||||||
for key, value in related_record._get_document_vals_access_rights().items()
|
for key, value in related_record._get_document_vals_access_rights().items()
|
||||||
if key not in pre_vals
|
if key not in pre_vals
|
||||||
})
|
})
|
||||||
if 'access_ids' in pre_vals:
|
if 'access_ids' in pre_vals:
|
||||||
continue
|
continue
|
||||||
access_ids = vals.get('access_ids') or []
|
access_ids = vals.get('access_ids') or []
|
||||||
partner_with_access = {access[2]['partner_id'] for access in access_ids} # list of Command.create tuples
|
partner_with_access = {access[2]['partner_id'] for access in access_ids} # list of Command.create tuples
|
||||||
related_document_access = related_record._get_document_access_ids()
|
related_document_access = related_record._get_document_access_ids()
|
||||||
if related_document_access is False:
|
if related_document_access is False:
|
||||||
# Keep logs but remove members
|
# Keep logs but remove members
|
||||||
access_ids = [a for a in access_ids if not a[2].get('role')]
|
access_ids = [a for a in access_ids if not a[2].get('role')]
|
||||||
else:
|
else:
|
||||||
accesses_to_add = [
|
accesses_to_add = [
|
||||||
(partner, access)
|
(partner, access)
|
||||||
for partner, access in related_record._get_document_access_ids()
|
for partner, access in related_record._get_document_access_ids()
|
||||||
if partner.id not in partner_with_access
|
if partner.id not in partner_with_access
|
||||||
]
|
]
|
||||||
if accesses_to_add:
|
if accesses_to_add:
|
||||||
access_ids.extend(
|
access_ids.extend(
|
||||||
Command.create({
|
Command.create({
|
||||||
'partner_id': partner.id,
|
'partner_id': partner.id,
|
||||||
'role': role,
|
'role': role,
|
||||||
'expiration_date': expiration_date,
|
'expiration_date': expiration_date,
|
||||||
})
|
})
|
||||||
for partner, (role, expiration_date) in accesses_to_add
|
for partner, (role, expiration_date) in accesses_to_add
|
||||||
)
|
)
|
||||||
vals['access_ids'] = access_ids
|
vals['access_ids'] = access_ids
|
||||||
return vals_list
|
return vals_list
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,29 @@
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class DocumentRedirect(models.Model):
|
class DocumentRedirect(models.Model):
|
||||||
"""Model used to keep the old links valid after the 18.0 migration.
|
"""Model used to keep the old links valid after the 18.0 migration.
|
||||||
|
|
||||||
Do *NOT* use that model or inherit from it, it will be removed in the future.
|
Do *NOT* use that model or inherit from it, it will be removed in the future.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_name = "documents.redirect"
|
_name = "documents.redirect"
|
||||||
_description = "Document Redirect"
|
_description = "Document Redirect"
|
||||||
_log_access = False
|
_log_access = False
|
||||||
|
|
||||||
access_token = fields.Char(required=True, index="btree")
|
access_token = fields.Char(required=True, index="btree")
|
||||||
document_id = fields.Many2one("documents.document", ondelete="cascade")
|
document_id = fields.Many2one("documents.document", ondelete="cascade")
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_redirection(self, access_token):
|
def _get_redirection(self, access_token):
|
||||||
"""Redirect to the right document, only if its access is view.
|
"""Redirect to the right document, only if its access is view.
|
||||||
|
|
||||||
We won't redirect if the access is not "view" to not give write access
|
We won't redirect if the access is not "view" to not give write access
|
||||||
if the permission has been changed on the document (or to not give the
|
if the permission has been changed on the document (or to not give the
|
||||||
token if the access is "none").
|
token if the access is "none").
|
||||||
"""
|
"""
|
||||||
return self.search(
|
return self.search(
|
||||||
# do not give write access for old token
|
# do not give write access for old token
|
||||||
[("access_token", "=", access_token), ('document_id.access_via_link', '=', 'view')],
|
[("access_token", "=", access_token), ('document_id.access_via_link', '=', 'view')],
|
||||||
limit=1,
|
limit=1,
|
||||||
).document_id
|
).document_id
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,48 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
from odoo import _, api, models, fields
|
from odoo import _, api, models, fields
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
class Tags(models.Model):
|
class Tags(models.Model):
|
||||||
_name = "documents.tag"
|
_name = "documents.tag"
|
||||||
_description = "Tag"
|
_description = "Tag"
|
||||||
_order = "sequence, name"
|
_order = "sequence, name"
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_default_color(self):
|
def _get_default_color(self):
|
||||||
return randint(1, 11)
|
return randint(1, 11)
|
||||||
|
|
||||||
name = fields.Char(required=True, translate=True)
|
name = fields.Char(required=True, translate=True)
|
||||||
sequence = fields.Integer('Sequence', default=10)
|
sequence = fields.Integer('Sequence', default=10)
|
||||||
color = fields.Integer('Color', default=_get_default_color)
|
color = fields.Integer('Color', default=_get_default_color)
|
||||||
tooltip = fields.Char(help="Text shown when hovering on this tag", string="Tooltip")
|
tooltip = fields.Char(help="Text shown when hovering on this tag", string="Tooltip")
|
||||||
document_ids = fields.Many2many('documents.document', 'document_tag_rel')
|
document_ids = fields.Many2many('documents.document', 'document_tag_rel')
|
||||||
|
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
('tag_name_unique', 'unique (name)', "Tag name already used"),
|
('tag_name_unique', 'unique (name)', "Tag name already used"),
|
||||||
]
|
]
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_tags(self, domain):
|
def _get_tags(self, domain):
|
||||||
"""
|
"""
|
||||||
fetches the tag and facet ids for the document selector (custom left sidebar of the kanban view)
|
fetches the tag and facet ids for the document selector (custom left sidebar of the kanban view)
|
||||||
"""
|
"""
|
||||||
tags = self.env['documents.document'].search(domain).tag_ids
|
tags = self.env['documents.document'].search(domain).tag_ids
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'sequence': tag.sequence,
|
'sequence': tag.sequence,
|
||||||
'id': tag.id,
|
'id': tag.id,
|
||||||
'color': tag.color,
|
'color': tag.color,
|
||||||
'__count': len(tag.document_ids)
|
'__count': len(tag.document_ids)
|
||||||
} for tag in tags
|
} for tag in tags
|
||||||
]
|
]
|
||||||
|
|
||||||
@api.ondelete(at_uninstall=False)
|
@api.ondelete(at_uninstall=False)
|
||||||
def _unlink_except_used_in_server_action(self):
|
def _unlink_except_used_in_server_action(self):
|
||||||
external_ids = self._get_external_ids()
|
external_ids = self._get_external_ids()
|
||||||
if external_ids and self.env['ir.actions.server'].search_count([('resource_ref', 'in', external_ids)], limit=1):
|
if external_ids and self.env['ir.actions.server'].search_count([('resource_ref', 'in', external_ids)], limit=1):
|
||||||
raise UserError(_("You cannot delete tags used in server actions."))
|
raise UserError(_("You cannot delete tags used in server actions."))
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
class DocumentUnlinkMixin(models.AbstractModel):
|
class DocumentUnlinkMixin(models.AbstractModel):
|
||||||
"""Send the related documents to trash when the record is deleted."""
|
"""Send the related documents to trash when the record is deleted."""
|
||||||
_name = 'documents.unlink.mixin'
|
_name = 'documents.unlink.mixin'
|
||||||
_description = "Documents unlink mixin"
|
_description = "Documents unlink mixin"
|
||||||
|
|
||||||
def unlink(self):
|
def unlink(self):
|
||||||
"""Prevent deletion of the attachments / documents and send them to the trash instead."""
|
"""Prevent deletion of the attachments / documents and send them to the trash instead."""
|
||||||
documents = self.env['documents.document'].search([
|
documents = self.env['documents.document'].search([
|
||||||
('res_model', '=', self._name),
|
('res_model', '=', self._name),
|
||||||
('res_id', 'in', self.ids),
|
('res_id', 'in', self.ids),
|
||||||
('active', '=', True),
|
('active', '=', True),
|
||||||
])
|
])
|
||||||
|
|
||||||
for document in documents:
|
for document in documents:
|
||||||
document.write({
|
document.write({
|
||||||
'res_model': 'documents.document',
|
'res_model': 'documents.document',
|
||||||
'res_id': document.id,
|
'res_id': document.id,
|
||||||
'active': False,
|
'active': False,
|
||||||
})
|
})
|
||||||
|
|
||||||
return super().unlink()
|
return super().unlink()
|
||||||
|
|
|
||||||
|
|
@ -1,87 +1,87 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
|
|
||||||
from odoo import models, api
|
from odoo import models, api
|
||||||
from odoo.tools.pdf import PdfFileWriter, PdfFileReader
|
from odoo.tools.pdf import PdfFileWriter, PdfFileReader
|
||||||
|
|
||||||
|
|
||||||
class IrAttachment(models.Model):
|
class IrAttachment(models.Model):
|
||||||
_inherit = ['ir.attachment']
|
_inherit = ['ir.attachment']
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _pdf_split(self, new_files=None, open_files=None):
|
def _pdf_split(self, new_files=None, open_files=None):
|
||||||
"""Creates and returns new pdf attachments based on existing data.
|
"""Creates and returns new pdf attachments based on existing data.
|
||||||
|
|
||||||
:param new_files: the array that represents the new pdf structure:
|
:param new_files: the array that represents the new pdf structure:
|
||||||
[{
|
[{
|
||||||
'name': 'New File Name',
|
'name': 'New File Name',
|
||||||
'new_pages': [{
|
'new_pages': [{
|
||||||
'old_file_index': 7,
|
'old_file_index': 7,
|
||||||
'old_page_number': 5,
|
'old_page_number': 5,
|
||||||
}],
|
}],
|
||||||
}]
|
}]
|
||||||
:param open_files: array of open file objects.
|
:param open_files: array of open file objects.
|
||||||
:returns: the new PDF attachments
|
:returns: the new PDF attachments
|
||||||
"""
|
"""
|
||||||
vals_list = []
|
vals_list = []
|
||||||
pdf_from_files = [PdfFileReader(open_file, strict=False) for open_file in open_files]
|
pdf_from_files = [PdfFileReader(open_file, strict=False) for open_file in open_files]
|
||||||
for new_file in new_files:
|
for new_file in new_files:
|
||||||
output = PdfFileWriter()
|
output = PdfFileWriter()
|
||||||
for page in new_file['new_pages']:
|
for page in new_file['new_pages']:
|
||||||
input_pdf = pdf_from_files[int(page['old_file_index'])]
|
input_pdf = pdf_from_files[int(page['old_file_index'])]
|
||||||
page_index = page['old_page_number'] - 1
|
page_index = page['old_page_number'] - 1
|
||||||
output.addPage(input_pdf.getPage(page_index))
|
output.addPage(input_pdf.getPage(page_index))
|
||||||
with io.BytesIO() as stream:
|
with io.BytesIO() as stream:
|
||||||
output.write(stream)
|
output.write(stream)
|
||||||
vals_list.append({
|
vals_list.append({
|
||||||
'name': new_file['name'] + ".pdf",
|
'name': new_file['name'] + ".pdf",
|
||||||
'datas': base64.b64encode(stream.getvalue()),
|
'datas': base64.b64encode(stream.getvalue()),
|
||||||
})
|
})
|
||||||
return self.create(vals_list)
|
return self.create(vals_list)
|
||||||
|
|
||||||
def _create_document(self, vals):
|
def _create_document(self, vals):
|
||||||
"""
|
"""
|
||||||
Implemented by bridge modules that create new documents if attachments are linked to
|
Implemented by bridge modules that create new documents if attachments are linked to
|
||||||
their business models.
|
their business models.
|
||||||
|
|
||||||
:param vals: the create/write dictionary of ir attachment
|
:param vals: the create/write dictionary of ir attachment
|
||||||
:return True if new documents are created
|
:return True if new documents are created
|
||||||
"""
|
"""
|
||||||
# Special case for documents
|
# Special case for documents
|
||||||
if vals.get('res_model') == 'documents.document' and vals.get('res_id'):
|
if vals.get('res_model') == 'documents.document' and vals.get('res_id'):
|
||||||
document = self.env['documents.document'].browse(vals['res_id'])
|
document = self.env['documents.document'].browse(vals['res_id'])
|
||||||
if document.exists() and not document.attachment_id:
|
if document.exists() and not document.attachment_id:
|
||||||
document.attachment_id = self[0].id
|
document.attachment_id = self[0].id
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Generic case for all other models
|
# Generic case for all other models
|
||||||
res_model = vals.get('res_model')
|
res_model = vals.get('res_model')
|
||||||
res_id = vals.get('res_id')
|
res_id = vals.get('res_id')
|
||||||
model = self.env.get(res_model)
|
model = self.env.get(res_model)
|
||||||
if model is not None and res_id and issubclass(self.pool[res_model], self.pool['documents.mixin']):
|
if model is not None and res_id and issubclass(self.pool[res_model], self.pool['documents.mixin']):
|
||||||
vals_list = [
|
vals_list = [
|
||||||
model.browse(res_id)._get_document_vals(attachment)
|
model.browse(res_id)._get_document_vals(attachment)
|
||||||
for attachment in self
|
for attachment in self
|
||||||
if not attachment.res_field and model.browse(res_id)._check_create_documents()
|
if not attachment.res_field and model.browse(res_id)._check_create_documents()
|
||||||
]
|
]
|
||||||
vals_list = [vals for vals in vals_list if vals] # Remove empty values
|
vals_list = [vals for vals in vals_list if vals] # Remove empty values
|
||||||
self.env['documents.document'].create(vals_list)
|
self.env['documents.document'].create(vals_list)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@api.model_create_multi
|
@api.model_create_multi
|
||||||
def create(self, vals_list):
|
def create(self, vals_list):
|
||||||
attachments = super().create(vals_list)
|
attachments = super().create(vals_list)
|
||||||
for attachment, vals in zip(attachments, vals_list):
|
for attachment, vals in zip(attachments, vals_list):
|
||||||
# the context can indicate that this new attachment is created from documents, and therefore
|
# the context can indicate that this new attachment is created from documents, and therefore
|
||||||
# doesn't need a new document to contain it.
|
# doesn't need a new document to contain it.
|
||||||
if not self._context.get('no_document') and not attachment.res_field:
|
if not self._context.get('no_document') and not attachment.res_field:
|
||||||
attachment.sudo()._create_document(dict(vals, res_model=attachment.res_model, res_id=attachment.res_id))
|
attachment.sudo()._create_document(dict(vals, res_model=attachment.res_model, res_id=attachment.res_id))
|
||||||
return attachments
|
return attachments
|
||||||
|
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
if not self._context.get('no_document'):
|
if not self._context.get('no_document'):
|
||||||
self.filtered(lambda a: not (vals.get('res_field') or a.res_field)).sudo()._create_document(vals)
|
self.filtered(lambda a: not (vals.get('res_field') or a.res_field)).sudo()._create_document(vals)
|
||||||
return super(IrAttachment, self).write(vals)
|
return super(IrAttachment, self).write(vals)
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
from os.path import splitext
|
from os.path import splitext
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
class IrBinary(models.AbstractModel):
|
class IrBinary(models.AbstractModel):
|
||||||
_inherit = 'ir.binary'
|
_inherit = 'ir.binary'
|
||||||
|
|
||||||
def _record_to_stream(self, record, field_name):
|
def _record_to_stream(self, record, field_name):
|
||||||
if record._name == 'documents.document' and field_name in ('raw', 'datas', 'db_datas'):
|
if record._name == 'documents.document' and field_name in ('raw', 'datas', 'db_datas'):
|
||||||
# Read access to document give implicit read access to the attachment
|
# Read access to document give implicit read access to the attachment
|
||||||
return super()._record_to_stream(record.attachment_id.sudo(), field_name)
|
return super()._record_to_stream(record.attachment_id.sudo(), field_name)
|
||||||
|
|
||||||
return super()._record_to_stream(record, field_name)
|
return super()._record_to_stream(record, field_name)
|
||||||
|
|
||||||
def _get_stream_from(
|
def _get_stream_from(
|
||||||
self, record, field_name='raw', filename=None, filename_field='name', mimetype=None,
|
self, record, field_name='raw', filename=None, filename_field='name', mimetype=None,
|
||||||
default_mimetype='application/octet-stream',
|
default_mimetype='application/octet-stream',
|
||||||
):
|
):
|
||||||
# skip magic detection of the file extension when it is provided
|
# skip magic detection of the file extension when it is provided
|
||||||
if (record._name == 'documents.document'
|
if (record._name == 'documents.document'
|
||||||
and filename is None
|
and filename is None
|
||||||
and record.file_extension
|
and record.file_extension
|
||||||
):
|
):
|
||||||
name, extension = splitext(record.name)
|
name, extension = splitext(record.name)
|
||||||
if extension == f'.{record.file_extension}':
|
if extension == f'.{record.file_extension}':
|
||||||
filename = record.name
|
filename = record.name
|
||||||
else:
|
else:
|
||||||
filename = f'{name}.{record.file_extension}'
|
filename = f'{name}.{record.file_extension}'
|
||||||
|
|
||||||
return super()._get_stream_from(
|
return super()._get_stream_from(
|
||||||
record, field_name, filename, filename_field, mimetype, default_mimetype)
|
record, field_name, filename, filename_field, mimetype, default_mimetype)
|
||||||
|
|
|
||||||
|
|
@ -1,99 +1,99 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from odoo import api, models, fields, _
|
from odoo import api, models, fields, _
|
||||||
from odoo.osv import expression
|
from odoo.osv import expression
|
||||||
|
|
||||||
|
|
||||||
class MailActivity(models.Model):
|
class MailActivity(models.Model):
|
||||||
_inherit = 'mail.activity'
|
_inherit = 'mail.activity'
|
||||||
|
|
||||||
def _prepare_next_activity_values(self):
|
def _prepare_next_activity_values(self):
|
||||||
vals = super()._prepare_next_activity_values()
|
vals = super()._prepare_next_activity_values()
|
||||||
current_activity_type = self.activity_type_id
|
current_activity_type = self.activity_type_id
|
||||||
next_activity_type = current_activity_type.triggered_next_type_id
|
next_activity_type = current_activity_type.triggered_next_type_id
|
||||||
|
|
||||||
if current_activity_type.category == 'upload_file' and self.res_model == 'documents.document' and next_activity_type.category == 'upload_file':
|
if current_activity_type.category == 'upload_file' and self.res_model == 'documents.document' and next_activity_type.category == 'upload_file':
|
||||||
existing_document = self.env['documents.document'].search([('request_activity_id', '=', self.id)], limit=1)
|
existing_document = self.env['documents.document'].search([('request_activity_id', '=', self.id)], limit=1)
|
||||||
if 'summary' not in vals:
|
if 'summary' not in vals:
|
||||||
vals['summary'] = self.summary or _('Upload file request')
|
vals['summary'] = self.summary or _('Upload file request')
|
||||||
new_doc_request = self.env['documents.document'].create({
|
new_doc_request = self.env['documents.document'].create({
|
||||||
'owner_id': existing_document.owner_id.id,
|
'owner_id': existing_document.owner_id.id,
|
||||||
'folder_id': next_activity_type.folder_id.id if next_activity_type.folder_id else existing_document.folder_id.id,
|
'folder_id': next_activity_type.folder_id.id if next_activity_type.folder_id else existing_document.folder_id.id,
|
||||||
'tag_ids': [(6, 0, next_activity_type.tag_ids.ids)],
|
'tag_ids': [(6, 0, next_activity_type.tag_ids.ids)],
|
||||||
'name': vals['summary'],
|
'name': vals['summary'],
|
||||||
})
|
})
|
||||||
vals['res_id'] = new_doc_request.id
|
vals['res_id'] = new_doc_request.id
|
||||||
return vals
|
return vals
|
||||||
|
|
||||||
def _action_done(self, feedback=False, attachment_ids=None):
|
def _action_done(self, feedback=False, attachment_ids=None):
|
||||||
if not self:
|
if not self:
|
||||||
return super()._action_done(feedback=feedback, attachment_ids=attachment_ids)
|
return super()._action_done(feedback=feedback, attachment_ids=attachment_ids)
|
||||||
documents = self.env['documents.document'].search([('request_activity_id', 'in', self.ids)])
|
documents = self.env['documents.document'].search([('request_activity_id', 'in', self.ids)])
|
||||||
document_without_attachment = documents.filtered(lambda d: not d.attachment_id)
|
document_without_attachment = documents.filtered(lambda d: not d.attachment_id)
|
||||||
if document_without_attachment and not feedback:
|
if document_without_attachment and not feedback:
|
||||||
feedback = _("Document Request: %(name)s Uploaded by: %(user)s",
|
feedback = _("Document Request: %(name)s Uploaded by: %(user)s",
|
||||||
name=documents[0].name, user=self.env.user.name)
|
name=documents[0].name, user=self.env.user.name)
|
||||||
messages, next_activities = super(MailActivity, self.with_context(no_document=True))._action_done(
|
messages, next_activities = super(MailActivity, self.with_context(no_document=True))._action_done(
|
||||||
feedback=feedback, attachment_ids=attachment_ids)
|
feedback=feedback, attachment_ids=attachment_ids)
|
||||||
# Downgrade access link role from edit to view if necessary (if the requestee didn't have a user at the request
|
# Downgrade access link role from edit to view if necessary (if the requestee didn't have a user at the request
|
||||||
# time, we previously granted him edit access by setting access_via_link to edit on the document).
|
# time, we previously granted him edit access by setting access_via_link to edit on the document).
|
||||||
documents.filtered(lambda document: document.access_via_link == 'edit').access_via_link = 'view'
|
documents.filtered(lambda document: document.access_via_link == 'edit').access_via_link = 'view'
|
||||||
# Remove request information on the document
|
# Remove request information on the document
|
||||||
documents.requestee_partner_id = False
|
documents.requestee_partner_id = False
|
||||||
documents.request_activity_id = False
|
documents.request_activity_id = False
|
||||||
# Attachment must be set after documents.request_activity_id is set to False to prevent document write to
|
# Attachment must be set after documents.request_activity_id is set to False to prevent document write to
|
||||||
# trigger an action_done.
|
# trigger an action_done.
|
||||||
if attachment_ids and document_without_attachment:
|
if attachment_ids and document_without_attachment:
|
||||||
document_without_attachment.attachment_id = attachment_ids[0]
|
document_without_attachment.attachment_id = attachment_ids[0]
|
||||||
return messages, next_activities
|
return messages, next_activities
|
||||||
|
|
||||||
@api.model_create_multi
|
@api.model_create_multi
|
||||||
def create(self, vals_list):
|
def create(self, vals_list):
|
||||||
activities = super().create(vals_list)
|
activities = super().create(vals_list)
|
||||||
upload_activities = activities.filtered(lambda act: act.activity_category == 'upload_file')
|
upload_activities = activities.filtered(lambda act: act.activity_category == 'upload_file')
|
||||||
|
|
||||||
# link back documents and activities
|
# link back documents and activities
|
||||||
upload_documents_activities = upload_activities.filtered(lambda act: act.res_model == 'documents.document')
|
upload_documents_activities = upload_activities.filtered(lambda act: act.res_model == 'documents.document')
|
||||||
if upload_documents_activities:
|
if upload_documents_activities:
|
||||||
documents = self.env['documents.document'].browse(upload_documents_activities.mapped('res_id'))
|
documents = self.env['documents.document'].browse(upload_documents_activities.mapped('res_id'))
|
||||||
for document, activity in zip(documents, upload_documents_activities):
|
for document, activity in zip(documents, upload_documents_activities):
|
||||||
if not document.request_activity_id:
|
if not document.request_activity_id:
|
||||||
document.request_activity_id = activity.id
|
document.request_activity_id = activity.id
|
||||||
|
|
||||||
# create underlying documents if related record is not a document
|
# create underlying documents if related record is not a document
|
||||||
doc_vals = [{
|
doc_vals = [{
|
||||||
'res_model': activity.res_model,
|
'res_model': activity.res_model,
|
||||||
'res_id': activity.res_id,
|
'res_id': activity.res_id,
|
||||||
'owner_id': activity.activity_type_id.default_user_id.id or self.env.user.id,
|
'owner_id': activity.activity_type_id.default_user_id.id or self.env.user.id,
|
||||||
'folder_id': activity.activity_type_id.folder_id.id,
|
'folder_id': activity.activity_type_id.folder_id.id,
|
||||||
'tag_ids': [(6, 0, activity.activity_type_id.tag_ids.ids)],
|
'tag_ids': [(6, 0, activity.activity_type_id.tag_ids.ids)],
|
||||||
'name': activity.summary or activity.res_name or 'upload file request',
|
'name': activity.summary or activity.res_name or 'upload file request',
|
||||||
'request_activity_id': activity.id,
|
'request_activity_id': activity.id,
|
||||||
} for activity in upload_activities.filtered(
|
} for activity in upload_activities.filtered(
|
||||||
lambda act: act.res_model != 'documents.document' and act.activity_type_id.folder_id
|
lambda act: act.res_model != 'documents.document' and act.activity_type_id.folder_id
|
||||||
)]
|
)]
|
||||||
if doc_vals:
|
if doc_vals:
|
||||||
self.env['documents.document'].sudo().create(doc_vals)
|
self.env['documents.document'].sudo().create(doc_vals)
|
||||||
return activities
|
return activities
|
||||||
|
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
write_result = super().write(vals)
|
write_result = super().write(vals)
|
||||||
if 'date_deadline' not in vals or not (
|
if 'date_deadline' not in vals or not (
|
||||||
act_on_docs := self.filtered(lambda activity: activity.res_model == 'documents.document')):
|
act_on_docs := self.filtered(lambda activity: activity.res_model == 'documents.document')):
|
||||||
return write_result
|
return write_result
|
||||||
# Update expiration access of the requestee when updating the related request activity deadline
|
# Update expiration access of the requestee when updating the related request activity deadline
|
||||||
document_requestee_partner_ids = self.env['documents.document'].search_read([
|
document_requestee_partner_ids = self.env['documents.document'].search_read([
|
||||||
('id', 'in', act_on_docs.mapped('res_id')),
|
('id', 'in', act_on_docs.mapped('res_id')),
|
||||||
('requestee_partner_id', '!=', False),
|
('requestee_partner_id', '!=', False),
|
||||||
('request_activity_id', 'in', self.ids),
|
('request_activity_id', 'in', self.ids),
|
||||||
], ['requestee_partner_id'])
|
], ['requestee_partner_id'])
|
||||||
new_expiration_date = datetime.combine(self[0].date_deadline, datetime.max.time())
|
new_expiration_date = datetime.combine(self[0].date_deadline, datetime.max.time())
|
||||||
self.env['documents.access'].search(expression.OR([[
|
self.env['documents.access'].search(expression.OR([[
|
||||||
('document_id', '=', document_requestee_partner_id['id']),
|
('document_id', '=', document_requestee_partner_id['id']),
|
||||||
('partner_id', '=', document_requestee_partner_id['requestee_partner_id'][0]),
|
('partner_id', '=', document_requestee_partner_id['requestee_partner_id'][0]),
|
||||||
('expiration_date', '<', new_expiration_date),
|
('expiration_date', '<', new_expiration_date),
|
||||||
] for document_requestee_partner_id in document_requestee_partner_ids
|
] for document_requestee_partner_id in document_requestee_partner_ids
|
||||||
])).expiration_date = new_expiration_date
|
])).expiration_date = new_expiration_date
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo import models, fields
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
class MailActivityType(models.Model):
|
class MailActivityType(models.Model):
|
||||||
_inherit = "mail.activity.type"
|
_inherit = "mail.activity.type"
|
||||||
|
|
||||||
tag_ids = fields.Many2many('documents.tag')
|
tag_ids = fields.Many2many('documents.tag')
|
||||||
folder_id = fields.Many2one('documents.document',
|
folder_id = fields.Many2one('documents.document',
|
||||||
domain="[('type', '=', 'folder'), ('shortcut_document_id', '=', False)]",
|
domain="[('type', '=', 'folder'), ('shortcut_document_id', '=', False)]",
|
||||||
help="By defining a folder, the upload activities will generate a document")
|
help="By defining a folder, the upload activities will generate a document")
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo import fields, models
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
class ResConfigSettings(models.TransientModel):
|
class ResConfigSettings(models.TransientModel):
|
||||||
_inherit = 'res.config.settings'
|
_inherit = 'res.config.settings'
|
||||||
|
|
||||||
deletion_delay = fields.Integer(config_parameter="documents.deletion_delay", default=30,
|
deletion_delay = fields.Integer(config_parameter="documents.deletion_delay", default=30,
|
||||||
help='Delay after permanent deletion of the document in the trash (days)')
|
help='Delay after permanent deletion of the document in the trash (days)')
|
||||||
|
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
('check_deletion_delay', 'CHECK(deletion_delay >= 0)', 'The deletion delay should be positive.'),
|
('check_deletion_delay', 'CHECK(deletion_delay >= 0)', 'The deletion delay should be positive.'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,48 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo import models, fields, _
|
from odoo import models, fields, _
|
||||||
from odoo.osv import expression
|
from odoo.osv import expression
|
||||||
|
|
||||||
|
|
||||||
class Partner(models.Model):
|
class Partner(models.Model):
|
||||||
_inherit = "res.partner"
|
_inherit = "res.partner"
|
||||||
|
|
||||||
document_count = fields.Integer('Document Count', compute='_compute_document_count')
|
document_count = fields.Integer('Document Count', compute='_compute_document_count')
|
||||||
|
|
||||||
def _compute_document_count(self):
|
def _compute_document_count(self):
|
||||||
read_group_var = self.env['documents.document']._read_group(
|
read_group_var = self.env['documents.document']._read_group(
|
||||||
expression.AND([
|
expression.AND([
|
||||||
[('partner_id', 'in', self.ids)],
|
[('partner_id', 'in', self.ids)],
|
||||||
[('type', '!=', 'folder')],
|
[('type', '!=', 'folder')],
|
||||||
]),
|
]),
|
||||||
groupby=['partner_id'],
|
groupby=['partner_id'],
|
||||||
aggregates=['__count'])
|
aggregates=['__count'])
|
||||||
|
|
||||||
document_count_dict = {partner.id: count for partner, count in read_group_var}
|
document_count_dict = {partner.id: count for partner, count in read_group_var}
|
||||||
for record in self:
|
for record in self:
|
||||||
record.document_count = document_count_dict.get(record.id, 0)
|
record.document_count = document_count_dict.get(record.id, 0)
|
||||||
|
|
||||||
def action_see_documents(self):
|
def action_see_documents(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
'name': _('Documents'),
|
'name': _('Documents'),
|
||||||
'domain': [('partner_id', '=', self.id)],
|
'domain': [('partner_id', '=', self.id)],
|
||||||
'res_model': 'documents.document',
|
'res_model': 'documents.document',
|
||||||
'type': 'ir.actions.act_window',
|
'type': 'ir.actions.act_window',
|
||||||
'views': [(False, 'kanban')],
|
'views': [(False, 'kanban')],
|
||||||
'view_mode': 'kanban',
|
'view_mode': 'kanban',
|
||||||
'context': {
|
'context': {
|
||||||
"default_partner_id": self.id,
|
"default_partner_id": self.id,
|
||||||
"searchpanel_default_folder_id": False
|
"searchpanel_default_folder_id": False
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def action_create_members_to_invite(self):
|
def action_create_members_to_invite(self):
|
||||||
return {
|
return {
|
||||||
'res_model': 'res.partner',
|
'res_model': 'res.partner',
|
||||||
'target': 'new',
|
'target': 'new',
|
||||||
'type': 'ir.actions.act_window',
|
'type': 'ir.actions.act_window',
|
||||||
'view_id': self.env.ref('base.view_partner_simple_form').id,
|
'view_id': self.env.ref('base.view_partner_simple_form').id,
|
||||||
'view_mode': 'form',
|
'view_mode': 'form',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
class Users(models.Model):
|
class Users(models.Model):
|
||||||
_name = "res.users"
|
_name = "res.users"
|
||||||
_inherit = ["res.users"]
|
_inherit = ["res.users"]
|
||||||
|
|
||||||
def _init_store_data(self, store):
|
def _init_store_data(self, store):
|
||||||
super()._init_store_data(store)
|
super()._init_store_data(store)
|
||||||
has_group = self.env.user.has_group("documents.group_documents_user")
|
has_group = self.env.user.has_group("documents.group_documents_user")
|
||||||
store.add({"hasDocumentsUserGroup": has_group})
|
store.add({"hasDocumentsUserGroup": has_group})
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
access_documents_access_base_group_portal,documents_access_base_group_portal,model_documents_access,base.group_portal,1,0,0,0
|
access_documents_access_base_group_portal,documents_access_base_group_portal,model_documents_access,base.group_portal,1,0,0,0
|
||||||
access_documents_access_base_group_user,documents_access_base_group_user,model_documents_access,base.group_user,1,1,1,1
|
access_documents_access_base_group_user,documents_access_base_group_user,model_documents_access,base.group_user,1,1,1,1
|
||||||
access_documents_attachment_base_group_portal,documents_attachment_base_group_portal,model_documents_document,base.group_portal,1,1,1,1
|
access_documents_attachment_base_group_portal,documents_attachment_base_group_portal,model_documents_document,base.group_portal,1,1,1,1
|
||||||
access_documents_attachment_base_group_user,documents_attachment_base_group_user,model_documents_document,base.group_user,1,1,1,1
|
access_documents_attachment_base_group_user,documents_attachment_base_group_user,model_documents_document,base.group_user,1,1,1,1
|
||||||
access_documents_tag_base_group_portal,documents_tag_base_group_portal,model_documents_tag,base.group_portal,1,0,0,0
|
access_documents_tag_base_group_portal,documents_tag_base_group_portal,model_documents_tag,base.group_portal,1,0,0,0
|
||||||
access_documents_tag_base_group_user,documents_tag_base_group_user,model_documents_tag,base.group_user,1,0,0,0
|
access_documents_tag_base_group_user,documents_tag_base_group_user,model_documents_tag,base.group_user,1,0,0,0
|
||||||
|
|
||||||
access_documents_tag_group_user,documents_tag_group_user,model_documents_tag,documents.group_documents_user,1,0,0,0
|
access_documents_tag_group_user,documents_tag_group_user,model_documents_tag,documents.group_documents_user,1,0,0,0
|
||||||
|
|
||||||
access_documents_access_group_manager,documents_access_group_manager,model_documents_access,documents.group_documents_manager,1,1,1,1
|
access_documents_access_group_manager,documents_access_group_manager,model_documents_access,documents.group_documents_manager,1,1,1,1
|
||||||
access_documents_attachment_group_manager,documents_attachment_group_manager,model_documents_document,documents.group_documents_manager,1,1,1,1
|
access_documents_attachment_group_manager,documents_attachment_group_manager,model_documents_document,documents.group_documents_manager,1,1,1,1
|
||||||
access_documents_tag_group_manager,documents_tag_group_manager,model_documents_tag,documents.group_documents_manager,1,1,1,1
|
access_documents_tag_group_manager,documents_tag_group_manager,model_documents_tag,documents.group_documents_manager,1,1,1,1
|
||||||
|
|
||||||
access_documents_request_wizard,access.documents.request_wizard,model_documents_request_wizard,documents.group_documents_user,1,1,1,0
|
access_documents_request_wizard,access.documents.request_wizard,model_documents_request_wizard,documents.group_documents_user,1,1,1,0
|
||||||
access_documents_link_to_record_wizard,access.documents.link_to_record_wizard,model_documents_link_to_record_wizard,documents.group_documents_user,1,1,1,0
|
access_documents_link_to_record_wizard,access.documents.link_to_record_wizard,model_documents_link_to_record_wizard,documents.group_documents_user,1,1,1,0
|
||||||
access_mail_activity_plan_documents_manager,mail.activity.plan.documents.manager,mail.model_mail_activity_plan,documents.group_documents_manager,1,1,1,1
|
access_mail_activity_plan_documents_manager,mail.activity.plan.documents.manager,mail.model_mail_activity_plan,documents.group_documents_manager,1,1,1,1
|
||||||
access_mail_activity_plan_template_documents_manager,mail.activity.plan.template.documents.manager,mail.model_mail_activity_plan_template,documents.group_documents_manager,1,1,1,1
|
access_mail_activity_plan_template_documents_manager,mail.activity.plan.template.documents.manager,mail.model_mail_activity_plan_template,documents.group_documents_manager,1,1,1,1
|
||||||
access_documents_redirect_base_system_user,documents_redirect_base_system_user,model_documents_redirect,base.group_system,1,0,0,1
|
access_documents_redirect_base_system_user,documents_redirect_base_system_user,model_documents_redirect,base.group_system,1,0,0,1
|
||||||
|
|
|
||||||
|
|
|
@ -1,132 +1,132 @@
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="base.module_category_productivity_documents" model="ir.module.category">
|
<record id="base.module_category_productivity_documents" model="ir.module.category">
|
||||||
<field name="name">Documents</field>
|
<field name="name">Documents</field>
|
||||||
<field name="description">Allows you to manage your documents.</field>
|
<field name="description">Allows you to manage your documents.</field>
|
||||||
<field name="sequence">20</field>
|
<field name="sequence">20</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="group_documents_user" model="res.groups">
|
<record id="group_documents_user" model="res.groups">
|
||||||
<field name="name">User</field>
|
<field name="name">User</field>
|
||||||
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
|
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||||
<field name="category_id" ref="base.module_category_productivity_documents"/>
|
<field name="category_id" ref="base.module_category_productivity_documents"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="group_documents_manager" model="res.groups">
|
<record id="group_documents_manager" model="res.groups">
|
||||||
<field name="name">Administrator</field>
|
<field name="name">Administrator</field>
|
||||||
<field name="category_id" ref="base.module_category_productivity_documents"/>
|
<field name="category_id" ref="base.module_category_productivity_documents"/>
|
||||||
<field name="implied_ids" eval="[(4, ref('group_documents_user'))]"/>
|
<field name="implied_ids" eval="[(4, ref('group_documents_user'))]"/>
|
||||||
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
|
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="group_documents_system" model="res.groups">
|
<record id="group_documents_system" model="res.groups">
|
||||||
<field name="name">System Administrator</field>
|
<field name="name">System Administrator</field>
|
||||||
<field name="category_id" ref="base.module_category_productivity_documents"/>
|
<field name="category_id" ref="base.module_category_productivity_documents"/>
|
||||||
<field name="implied_ids" eval="[(4, ref('group_documents_manager'))]"/>
|
<field name="implied_ids" eval="[(4, ref('group_documents_manager'))]"/>
|
||||||
<field name="users" eval="[(4, ref('base.user_root'))]"/>
|
<field name="users" eval="[(4, ref('base.user_root'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="base.default_user" model="res.users">
|
<record id="base.default_user" model="res.users">
|
||||||
<field name="groups_id" eval="[(4, ref('documents.group_documents_manager'))]"/>
|
<field name="groups_id" eval="[(4, ref('documents.group_documents_manager'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- documents rules -->
|
<!-- documents rules -->
|
||||||
<!-- duplicated for spreadsheetCellThreads in rule documents_spreadsheet.documents_document_thread_global_rule
|
<!-- duplicated for spreadsheetCellThreads in rule documents_spreadsheet.documents_document_thread_global_rule
|
||||||
Please update aforemetioned the rule accordingly -->
|
Please update aforemetioned the rule accordingly -->
|
||||||
<record id="documents_document_global_rule" model="ir.rule">
|
<record id="documents_document_global_rule" model="ir.rule">
|
||||||
<field name="name">Documents.document: global read rule</field>
|
<field name="name">Documents.document: global read rule</field>
|
||||||
<field name="model_id" ref="model_documents_document"/>
|
<field name="model_id" ref="model_documents_document"/>
|
||||||
<field name="domain_force">[('user_permission', '!=', 'none')]</field>
|
<field name="domain_force">[('user_permission', '!=', 'none')]</field>
|
||||||
<field name="perm_write" eval="False"/>
|
<field name="perm_write" eval="False"/>
|
||||||
<field name="perm_create" eval="False"/>
|
<field name="perm_create" eval="False"/>
|
||||||
<field name="perm_unlink" eval="False"/>
|
<field name="perm_unlink" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_document_global_create_rule" model="ir.rule">
|
<record id="documents_document_global_create_rule" model="ir.rule">
|
||||||
<field name="name">Documents.document: global create rule</field>
|
<field name="name">Documents.document: global create rule</field>
|
||||||
<field name="model_id" ref="model_documents_document"/>
|
<field name="model_id" ref="model_documents_document"/>
|
||||||
<field name="domain_force">[
|
<field name="domain_force">[
|
||||||
'|', ('folder_id.user_permission', '=', 'edit'), ('folder_id', '=', False),
|
'|', ('folder_id.user_permission', '=', 'edit'), ('folder_id', '=', False),
|
||||||
]</field>
|
]</field>
|
||||||
<field name="perm_read" eval="False"/>
|
<field name="perm_read" eval="False"/>
|
||||||
<field name="perm_unlink" eval="False"/>
|
<field name="perm_unlink" eval="False"/>
|
||||||
<field name="perm_write" eval="False"/>
|
<field name="perm_write" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_document_global_write_rule" model="ir.rule">
|
<record id="documents_document_global_write_rule" model="ir.rule">
|
||||||
<field name="name">Documents.document: global write rule</field>
|
<field name="name">Documents.document: global write rule</field>
|
||||||
<field name="model_id" ref="model_documents_document"/>
|
<field name="model_id" ref="model_documents_document"/>
|
||||||
<field name="domain_force">[('user_permission', '=', 'edit')]</field>
|
<field name="domain_force">[('user_permission', '=', 'edit')]</field>
|
||||||
<field name="perm_create" eval="False"/>
|
<field name="perm_create" eval="False"/>
|
||||||
<field name="perm_read" eval="False"/>
|
<field name="perm_read" eval="False"/>
|
||||||
<field name="perm_unlink" eval="False"/>
|
<field name="perm_unlink" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- duplicated for spreadsheetCellThreads in rule documents_spreadsheet.spreadsheet_cell_thread_write_rule -->
|
<!-- duplicated for spreadsheetCellThreads in rule documents_spreadsheet.spreadsheet_cell_thread_write_rule -->
|
||||||
<record id="documents_document_write_base_rule" model="ir.rule">
|
<record id="documents_document_write_base_rule" model="ir.rule">
|
||||||
<field name="name">Documents.document: write+unlink base rule</field>
|
<field name="name">Documents.document: write+unlink base rule</field>
|
||||||
<field name="model_id" ref="model_documents_document"/>
|
<field name="model_id" ref="model_documents_document"/>
|
||||||
<field name="groups" eval="[
|
<field name="groups" eval="[
|
||||||
(4, ref('base.group_public')),
|
(4, ref('base.group_public')),
|
||||||
(4, ref('base.group_portal')),
|
(4, ref('base.group_portal')),
|
||||||
(4, ref('base.group_user')),
|
(4, ref('base.group_user')),
|
||||||
]"/>
|
]"/>
|
||||||
<field name="domain_force">[('user_permission', '=', 'edit'), ('is_pinned_folder', '=', False)]</field>
|
<field name="domain_force">[('user_permission', '=', 'edit'), ('is_pinned_folder', '=', False)]</field>
|
||||||
<field name="perm_read" eval="False"/>
|
<field name="perm_read" eval="False"/>
|
||||||
<field name="perm_create" eval="False"/>
|
<field name="perm_create" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_document_write_manager_rule" model="ir.rule">
|
<record id="documents_document_write_manager_rule" model="ir.rule">
|
||||||
<field name="name">Documents.document: write+unlink manager rule</field>
|
<field name="name">Documents.document: write+unlink manager rule</field>
|
||||||
<field name="model_id" ref="model_documents_document"/>
|
<field name="model_id" ref="model_documents_document"/>
|
||||||
<field name="groups" eval="[(4, ref('documents.group_documents_manager'))]"/>
|
<field name="groups" eval="[(4, ref('documents.group_documents_manager'))]"/>
|
||||||
<field name="domain_force">[('user_permission', '=', 'edit')]</field>
|
<field name="domain_force">[('user_permission', '=', 'edit')]</field>
|
||||||
<field name="perm_read" eval="False"/>
|
<field name="perm_read" eval="False"/>
|
||||||
<field name="perm_create" eval="False"/>
|
<field name="perm_create" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- same as documents.document -->
|
<!-- same as documents.document -->
|
||||||
<record id="documents_access_global_rule_read" model="ir.rule">
|
<record id="documents_access_global_rule_read" model="ir.rule">
|
||||||
<field name="name">Documents.access: global read rule</field>
|
<field name="name">Documents.access: global read rule</field>
|
||||||
<field name="model_id" ref="model_documents_access"/>
|
<field name="model_id" ref="model_documents_access"/>
|
||||||
<field name="domain_force">[('document_id.user_permission', '!=', 'none')]</field>
|
<field name="domain_force">[('document_id.user_permission', '!=', 'none')]</field>
|
||||||
<field name="perm_write" eval="False"/>
|
<field name="perm_write" eval="False"/>
|
||||||
<field name="perm_create" eval="False"/>
|
<field name="perm_create" eval="False"/>
|
||||||
<field name="perm_unlink" eval="False"/>
|
<field name="perm_unlink" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- same as documents.document -->
|
<!-- same as documents.document -->
|
||||||
<record id="documents_access_global_rule_write" model="ir.rule">
|
<record id="documents_access_global_rule_write" model="ir.rule">
|
||||||
<field name="name">Documents.access: global write rule</field>
|
<field name="name">Documents.access: global write rule</field>
|
||||||
<field name="model_id" ref="model_documents_access"/>
|
<field name="model_id" ref="model_documents_access"/>
|
||||||
<field name="domain_force">[('document_id.user_permission', '=', 'edit')]</field>
|
<field name="domain_force">[('document_id.user_permission', '=', 'edit')]</field>
|
||||||
<field name="perm_read" eval="False"/>
|
<field name="perm_read" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="mail_plan_rule_group_document_manager_document" model="ir.rule">
|
<record id="mail_plan_rule_group_document_manager_document" model="ir.rule">
|
||||||
<field name="name">Manager can manage document plans</field>
|
<field name="name">Manager can manage document plans</field>
|
||||||
<field name="groups" eval="[(4, ref('documents.group_documents_manager'))]"/>
|
<field name="groups" eval="[(4, ref('documents.group_documents_manager'))]"/>
|
||||||
<field name="model_id" ref="mail.model_mail_activity_plan"/>
|
<field name="model_id" ref="mail.model_mail_activity_plan"/>
|
||||||
<field name="domain_force">[('res_model', '=', 'documents.document')]</field>
|
<field name="domain_force">[('res_model', '=', 'documents.document')]</field>
|
||||||
<field name="perm_read" eval="False"/>
|
<field name="perm_read" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="mail_plan_template_rule_group_document_manager_document" model="ir.rule">
|
<record id="mail_plan_template_rule_group_document_manager_document" model="ir.rule">
|
||||||
<field name="name">Manager can manage document plan templates</field>
|
<field name="name">Manager can manage document plan templates</field>
|
||||||
<field name="groups" eval="[(4, ref('documents.group_documents_manager'))]"/>
|
<field name="groups" eval="[(4, ref('documents.group_documents_manager'))]"/>
|
||||||
<field name="model_id" ref="mail.model_mail_activity_plan_template"/>
|
<field name="model_id" ref="mail.model_mail_activity_plan_template"/>
|
||||||
<field name="domain_force">[('plan_id.res_model', '=', 'documents.document')]</field>
|
<field name="domain_force">[('plan_id.res_model', '=', 'documents.document')]</field>
|
||||||
<field name="perm_read" eval="False"/>
|
<field name="perm_read" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="documents_tag_rule_portal" model="ir.rule">
|
<record id="documents_tag_rule_portal" model="ir.rule">
|
||||||
<field name="name">Tag portal: Read access to the tags of the documents the user has access to</field>
|
<field name="name">Tag portal: Read access to the tags of the documents the user has access to</field>
|
||||||
<field name="model_id" ref="model_documents_tag"/>
|
<field name="model_id" ref="model_documents_tag"/>
|
||||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||||
<field name="domain_force">[('document_ids.user_permission', '!=', 'none')]</field>
|
<field name="domain_force">[('document_ids.user_permission', '!=', 'none')]</field>
|
||||||
<field name="perm_write" eval="False"/>
|
<field name="perm_write" eval="False"/>
|
||||||
<field name="perm_create" eval="False"/>
|
<field name="perm_create" eval="False"/>
|
||||||
<field name="perm_unlink" eval="False"/>
|
<field name="perm_unlink" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
<svg width="50" height="50" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><path d="M30.576 14.907a4.029 4.029 0 0 1 5.697 0l8.547 8.546a4.029 4.029 0 0 1 0 5.698l-15.669 15.67a4.029 4.029 0 0 1-5.698 0l-8.546-8.547a4.029 4.029 0 0 1 0-5.698l15.669-15.67Z" fill="#FBB945"/><path d="M18.721 9.047a4.029 4.029 0 0 1 5.264-2.18l11.167 4.625a4.029 4.029 0 0 1 2.18 5.264l-8.48 20.472a4.029 4.029 0 0 1-5.263 2.181l-11.167-4.626a4.029 4.029 0 0 1-2.18-5.264l8.48-20.472Z" fill="#FC868B"/><path d="M37.527 16.16c-.048.2-.113.4-.194.596l-8.48 20.472a4.029 4.029 0 0 1-5.265 2.18l-9.236-3.825a4.03 4.03 0 0 1 .555-5.007l15.669-15.67a4.029 4.029 0 0 1 5.697 0l1.254 1.254Z" fill="#F86126"/><path d="M4 8.029A4.029 4.029 0 0 1 8.029 4h12.087a4.029 4.029 0 0 1 4.029 4.029v22.16a4.029 4.029 0 0 1-4.03 4.028H8.03A4.029 4.029 0 0 1 4 30.188V8.03Z" fill="#2EBCFA"/><path d="M23.973 6.861c.112.37.172.762.172 1.168v22.16a4.029 4.029 0 0 1-4.029 4.029h-8.658a4.03 4.03 0 0 1-1.217-4.699l8.48-20.472a4.029 4.029 0 0 1 5.252-2.186Z" fill="#2D6388"/></svg>
|
<svg width="50" height="50" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><path d="M30.576 14.907a4.029 4.029 0 0 1 5.697 0l8.547 8.546a4.029 4.029 0 0 1 0 5.698l-15.669 15.67a4.029 4.029 0 0 1-5.698 0l-8.546-8.547a4.029 4.029 0 0 1 0-5.698l15.669-15.67Z" fill="#FBB945"/><path d="M18.721 9.047a4.029 4.029 0 0 1 5.264-2.18l11.167 4.625a4.029 4.029 0 0 1 2.18 5.264l-8.48 20.472a4.029 4.029 0 0 1-5.263 2.181l-11.167-4.626a4.029 4.029 0 0 1-2.18-5.264l8.48-20.472Z" fill="#FC868B"/><path d="M37.527 16.16c-.048.2-.113.4-.194.596l-8.48 20.472a4.029 4.029 0 0 1-5.265 2.18l-9.236-3.825a4.03 4.03 0 0 1 .555-5.007l15.669-15.67a4.029 4.029 0 0 1 5.697 0l1.254 1.254Z" fill="#F86126"/><path d="M4 8.029A4.029 4.029 0 0 1 8.029 4h12.087a4.029 4.029 0 0 1 4.029 4.029v22.16a4.029 4.029 0 0 1-4.03 4.028H8.03A4.029 4.029 0 0 1 4 30.188V8.03Z" fill="#2EBCFA"/><path d="M23.973 6.861c.112.37.172.762.172 1.168v22.16a4.029 4.029 0 0 1-4.029 4.029h-8.658a4.03 4.03 0 0 1-1.217-4.699l8.48-20.472a4.029 4.029 0 0 1 5.252-2.186Z" fill="#2D6388"/></svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
|
@ -1,37 +1,37 @@
|
||||||
/* @odoo-module */
|
/* @odoo-module */
|
||||||
|
|
||||||
import { Attachment } from "@mail/core/common/attachment_model";
|
import { Attachment } from "@mail/core/common/attachment_model";
|
||||||
import { patch } from "@web/core/utils/patch";
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
|
||||||
patch(Attachment.prototype, {
|
patch(Attachment.prototype, {
|
||||||
documentId: null,
|
documentId: null,
|
||||||
documentData: null,
|
documentData: null,
|
||||||
|
|
||||||
get urlRoute() {
|
get urlRoute() {
|
||||||
if (this.documentId) {
|
if (this.documentId) {
|
||||||
return this.isImage
|
return this.isImage
|
||||||
? `/web/image/${this.documentId}`
|
? `/web/image/${this.documentId}`
|
||||||
: `/web/content/${this.documentId}`;
|
: `/web/content/${this.documentId}`;
|
||||||
}
|
}
|
||||||
return super.urlRoute;
|
return super.urlRoute;
|
||||||
},
|
},
|
||||||
|
|
||||||
get defaultSource() {
|
get defaultSource() {
|
||||||
if (this.isPdf && this.documentId) {
|
if (this.isPdf && this.documentId) {
|
||||||
const encodedRoute = encodeURIComponent(
|
const encodedRoute = encodeURIComponent(
|
||||||
`/documents/content/${this.documentData.access_token}?download=0`
|
`/documents/content/${this.documentData.access_token}?download=0`
|
||||||
);
|
);
|
||||||
return `/web/static/lib/pdfjs/web/viewer.html?file=${encodedRoute}#pagemode=none`;
|
return `/web/static/lib/pdfjs/web/viewer.html?file=${encodedRoute}#pagemode=none`;
|
||||||
}
|
}
|
||||||
return super.defaultSource;
|
return super.defaultSource;
|
||||||
},
|
},
|
||||||
|
|
||||||
get urlQueryParams() {
|
get urlQueryParams() {
|
||||||
const res = super.urlQueryParams;
|
const res = super.urlQueryParams;
|
||||||
if (this.documentId) {
|
if (this.documentId) {
|
||||||
res["model"] = "documents.document";
|
res["model"] = "documents.document";
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,108 +1,108 @@
|
||||||
/* @odoo-module */
|
/* @odoo-module */
|
||||||
|
|
||||||
import { useService } from "@web/core/utils/hooks";
|
import { useService } from "@web/core/utils/hooks";
|
||||||
import { FileViewer as WebFileViewer } from "@web/core/file_viewer/file_viewer";
|
import { FileViewer as WebFileViewer } from "@web/core/file_viewer/file_viewer";
|
||||||
import { onWillUpdateProps } from "@odoo/owl";
|
import { onWillUpdateProps } from "@odoo/owl";
|
||||||
|
|
||||||
export class FileViewer extends WebFileViewer {
|
export class FileViewer extends WebFileViewer {
|
||||||
static template = "documents.FileViewer";
|
static template = "documents.FileViewer";
|
||||||
setup() {
|
setup() {
|
||||||
super.setup();
|
super.setup();
|
||||||
/** @type {import("@documents/core/document_service").DocumentService} */
|
/** @type {import("@documents/core/document_service").DocumentService} */
|
||||||
this.documentService = useService("document.document");
|
this.documentService = useService("document.document");
|
||||||
this.onSelectDocument = this.documentService.documentList?.onSelectDocument;
|
this.onSelectDocument = this.documentService.documentList?.onSelectDocument;
|
||||||
onWillUpdateProps((nextProps) => {
|
onWillUpdateProps((nextProps) => {
|
||||||
const indexOfFileToPreview = nextProps.startIndex;
|
const indexOfFileToPreview = nextProps.startIndex;
|
||||||
if (
|
if (
|
||||||
indexOfFileToPreview !== this.state.index &&
|
indexOfFileToPreview !== this.state.index &&
|
||||||
indexOfFileToPreview !== this.props.startIndex
|
indexOfFileToPreview !== this.props.startIndex
|
||||||
) {
|
) {
|
||||||
this.activateFile(indexOfFileToPreview);
|
this.activateFile(indexOfFileToPreview);
|
||||||
}
|
}
|
||||||
this.documentService.setPreviewedDocument(
|
this.documentService.setPreviewedDocument(
|
||||||
this.documentService.documentList.documents[nextProps.startIndex]
|
this.documentService.documentList.documents[nextProps.startIndex]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
get hasSplitPdf() {
|
get hasSplitPdf() {
|
||||||
if (this.documentService.documentList?.initialRecordSelectionLength === 1) {
|
if (this.documentService.documentList?.initialRecordSelectionLength === 1) {
|
||||||
return this.documentService.documentList.selectedDocument.attachment.isPdf;
|
return this.documentService.documentList.selectedDocument.attachment.isPdf;
|
||||||
}
|
}
|
||||||
return this.documentService.documentList?.documents.every(
|
return this.documentService.documentList?.documents.every(
|
||||||
(document) => document.attachment.isPdf
|
(document) => document.attachment.isPdf
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
get withDownload() {
|
get withDownload() {
|
||||||
if (this.documentService.documentList?.initialRecordSelectionLength === 1) {
|
if (this.documentService.documentList?.initialRecordSelectionLength === 1) {
|
||||||
return this.documentService.documentList.selectedDocument.attachment.isUrlYoutube;
|
return this.documentService.documentList.selectedDocument.attachment.isUrlYoutube;
|
||||||
}
|
}
|
||||||
return this.documentService.documentList?.documents.every(
|
return this.documentService.documentList?.documents.every(
|
||||||
(document) => document.attachment.isUrlYoutube
|
(document) => document.attachment.isUrlYoutube
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
onClickPdfSplit() {
|
onClickPdfSplit() {
|
||||||
this.close();
|
this.close();
|
||||||
if (this.documentService.documentList?.initialRecordSelectionLength === 1) {
|
if (this.documentService.documentList?.initialRecordSelectionLength === 1) {
|
||||||
return this.documentService.documentList?.pdfManagerOpenCallback([
|
return this.documentService.documentList?.pdfManagerOpenCallback([
|
||||||
this.documentService.documentList.selectedDocument.record,
|
this.documentService.documentList.selectedDocument.record,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return this.documentService.documentList?.pdfManagerOpenCallback(
|
return this.documentService.documentList?.pdfManagerOpenCallback(
|
||||||
this.documentService.documentList.documents.map((document) => document.record)
|
this.documentService.documentList.documents.map((document) => document.record)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
close() {
|
close() {
|
||||||
this.documentService.documentList?.onDeleteCallback();
|
this.documentService.documentList?.onDeleteCallback();
|
||||||
this.documentService.setPreviewedDocument(null);
|
this.documentService.setPreviewedDocument(null);
|
||||||
super.close();
|
super.close();
|
||||||
}
|
}
|
||||||
next() {
|
next() {
|
||||||
super.next();
|
super.next();
|
||||||
this.documentService.setPreviewedDocument(
|
this.documentService.setPreviewedDocument(
|
||||||
this.documentService.documentList.documents[this.state.index]
|
this.documentService.documentList.documents[this.state.index]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.onSelectDocument) {
|
if (this.onSelectDocument) {
|
||||||
const documentList = this.documentService.documentList;
|
const documentList = this.documentService.documentList;
|
||||||
if (
|
if (
|
||||||
!documentList ||
|
!documentList ||
|
||||||
!documentList.selectedDocument ||
|
!documentList.selectedDocument ||
|
||||||
!documentList.documents ||
|
!documentList.documents ||
|
||||||
!documentList.documents.length
|
!documentList.documents.length
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const index = documentList.documents.findIndex(
|
const index = documentList.documents.findIndex(
|
||||||
(document) => document === documentList.selectedDocument
|
(document) => document === documentList.selectedDocument
|
||||||
);
|
);
|
||||||
const nextIndex = index === documentList.documents.length - 1 ? 0 : index + 1;
|
const nextIndex = index === documentList.documents.length - 1 ? 0 : index + 1;
|
||||||
documentList.selectedDocument = documentList.documents[nextIndex];
|
documentList.selectedDocument = documentList.documents[nextIndex];
|
||||||
this.onSelectDocument(documentList.selectedDocument.record);
|
this.onSelectDocument(documentList.selectedDocument.record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previous() {
|
previous() {
|
||||||
super.previous();
|
super.previous();
|
||||||
this.documentService.setPreviewedDocument(
|
this.documentService.setPreviewedDocument(
|
||||||
this.documentService.documentList.documents[this.state.index]
|
this.documentService.documentList.documents[this.state.index]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.onSelectDocument) {
|
if (this.onSelectDocument) {
|
||||||
const documentList = this.documentService.documentList;
|
const documentList = this.documentService.documentList;
|
||||||
if (
|
if (
|
||||||
!documentList ||
|
!documentList ||
|
||||||
!documentList.selectedDocument ||
|
!documentList.selectedDocument ||
|
||||||
!documentList.documents ||
|
!documentList.documents ||
|
||||||
!documentList.documents.length
|
!documentList.documents.length
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const index = documentList.documents.findIndex(
|
const index = documentList.documents.findIndex(
|
||||||
(doc) => doc === documentList.selectedDocument
|
(doc) => doc === documentList.selectedDocument
|
||||||
);
|
);
|
||||||
// if we're on the first document, go "back" to the last one
|
// if we're on the first document, go "back" to the last one
|
||||||
const previousIndex = index === 0 ? documentList.documents.length - 1 : index - 1;
|
const previousIndex = index === 0 ? documentList.documents.length - 1 : index - 1;
|
||||||
documentList.selectedDocument = documentList.documents[previousIndex];
|
documentList.selectedDocument = documentList.documents[previousIndex];
|
||||||
this.onSelectDocument(documentList.selectedDocument.record);
|
this.onSelectDocument(documentList.selectedDocument.record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<templates>
|
<templates>
|
||||||
<t t-name="documents.FileViewer" t-inherit="web.FileViewer" t-inherit-mode="primary">
|
<t t-name="documents.FileViewer" t-inherit="web.FileViewer" t-inherit-mode="primary">
|
||||||
<xpath expr="//div[hasclass('o-FileViewer-download')]" position="before">
|
<xpath expr="//div[hasclass('o-FileViewer-download')]" position="before">
|
||||||
<div t-if="hasSplitPdf" class="o-FileViewer-headerButton d-flex align-items-center px-3 cursor-pointer" t-on-click.stop="onClickPdfSplit" role="button" title="Split PDF">
|
<div t-if="hasSplitPdf" class="o-FileViewer-headerButton d-flex align-items-center px-3 cursor-pointer" t-on-click.stop="onClickPdfSplit" role="button" title="Split PDF">
|
||||||
<i class="fa fa-scissors fa-fw" t-att-class="{ 'o-hasLabel me-2': !env.isSmall }" role="img"/>
|
<i class="fa fa-scissors fa-fw" t-att-class="{ 'o-hasLabel me-2': !env.isSmall }" role="img"/>
|
||||||
<t t-if="!env.isSmall">
|
<t t-if="!env.isSmall">
|
||||||
<span>Split PDF</span>
|
<span>Split PDF</span>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//div[hasclass('o-FileViewer-download')]" position="replace">
|
<xpath expr="//div[hasclass('o-FileViewer-download')]" position="replace">
|
||||||
<t t-if="!withDownload">$0</t>
|
<t t-if="!withDownload">$0</t>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//div[hasclass('o-FileViewer-navigation')][@aria-label='Previous']" position="attributes">
|
<xpath expr="//div[hasclass('o-FileViewer-navigation')][@aria-label='Previous']" position="attributes">
|
||||||
<attribute name="class" add="bg-dark" separator=" "/>
|
<attribute name="class" add="bg-dark" separator=" "/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//div[hasclass('o-FileViewer-navigation')][@aria-label='Next']" position="attributes">
|
<xpath expr="//div[hasclass('o-FileViewer-navigation')][@aria-label='Next']" position="attributes">
|
||||||
<attribute name="class" add="bg-dark" separator=" "/>
|
<attribute name="class" add="bg-dark" separator=" "/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//span[hasclass('oi-chevron-left')]" position="attributes">
|
<xpath expr="//span[hasclass('oi-chevron-left')]" position="attributes">
|
||||||
<attribute name="class" add="pe-1 text-white" separator=" "/>
|
<attribute name="class" add="pe-1 text-white" separator=" "/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//span[hasclass('oi-chevron-right')]" position="attributes">
|
<xpath expr="//span[hasclass('oi-chevron-right')]" position="attributes">
|
||||||
<attribute name="class" add="ps-1 text-white" separator=" "/>
|
<attribute name="class" add="ps-1 text-white" separator=" "/>
|
||||||
</xpath>
|
</xpath>
|
||||||
</t>
|
</t>
|
||||||
</templates>
|
</templates>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue