diff --git a/local-addons/morons/__manifest__.py b/local-addons/morons/__manifest__.py
index 136904f..ee6a93a 100644
--- a/local-addons/morons/__manifest__.py
+++ b/local-addons/morons/__manifest__.py
@@ -26,14 +26,14 @@
# 'security/ir.model.access.csv',
'views/project.xml',
'views/user.xml',
- "security/ir.model.access.csv",
- "security/security.xml",
'data/languages.xml',
'data/currencies.xml',
'data/email_template.xml',
'data/company_data.xml',
'data/services.xml',
'data/tags.xml',
+ "security/ir.model.access.csv",
+ "security/security.xml",
],
# only loaded in demonstration mode
'demo': [
diff --git a/local-addons/morons/models/__init__.py b/local-addons/morons/models/__init__.py
index bba51e2..f953daa 100644
--- a/local-addons/morons/models/__init__.py
+++ b/local-addons/morons/models/__init__.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from . import project
-from . import contributor
\ No newline at end of file
+from . import contributor
+from . import invoice
\ No newline at end of file
diff --git a/local-addons/morons/models/contributor.py b/local-addons/morons/models/contributor.py
index 44f8439..68e30a0 100644
--- a/local-addons/morons/models/contributor.py
+++ b/local-addons/morons/models/contributor.py
@@ -55,51 +55,59 @@ class InternalUser(models.Model):
contributor = fields.Boolean(string='Contributor', default=False)
active = fields.Boolean(string='Active', default=True)
currency = fields.Many2one('res.currency', string='Currency')
- # skype = fields.Char(string='Skype')
- # nationality = fields.Many2many('res.lang', required=True)
- # country_of_residence = fields.Many2one('res.country', required=True)
- # timezone = fields.Selection('_tz_get',
- # string='Timezone',
- # required=True,
- # default=lambda self: self.env.user.tz or 'UTC')
+ skype = fields.Char(string='Skype')
+ nationality = fields.Many2many('res.lang', required=True)
+ country_of_residence = fields.Many2one('res.country', required=True)
+ timezone = fields.Selection('_tz_get',
+ string='Timezone',
+ required=True,
+ default=lambda self: self.env.user.tz or 'UTC')
- # @api.model
- # def _tz_get(self):
- # return [(x, x) for x in pytz.all_timezones]
+ @api.model
+ def _tz_get(self):
+ return [(x, x) for x in pytz.all_timezones]
- # # Payment Methods
- # paypal = fields.Char('PayPal ID')
- # transferwise_id = fields.Char('Wise ID')
- # bank_account_number = fields.Char('Bank Account Number')
- # bank_name = fields.Char('Bank Name')
- # iban = fields.Char('IBAN')
- # swift = fields.Char('SWIFT')
- # bank_address = fields.Char('Bank Address')
- # preferred_payment_method = fields.Selection(selection=[('paypal', 'Paypal'),
- # ('transferwise', 'Wise'),
- # ('bank', 'Bank Transfer')])
+ # Payment Methods
+ paypal = fields.Char('PayPal ID')
+ transferwise_id = fields.Char('Wise ID')
+ bank_account_number = fields.Char('Bank Account Number')
+ bank_name = fields.Char('Bank Name')
+ iban = fields.Char('IBAN')
+ swift = fields.Char('SWIFT')
+ bank_address = fields.Char('Bank Address')
+ preferred_payment_method = fields.Selection(selection=[('paypal', 'Paypal'),
+ ('transferwise', 'Wise'),
+ ('bank', 'Bank Transfer')])
- # @api.constrains('paypal')
- # def validate_email(self):
- # if self.paypal:
- # match = re.match(
- # '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$',
- # self.paypal)
- # if match is None:
- # raise ValidationError('Not a valid email')
+ @api.constrains('paypal')
+ def validate_paypal(self):
+ if self.paypal:
+ match = re.match(
+ '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$',
+ self.paypal)
+ if match is None:
+ raise ValidationError('Not a valid email')
- # # Education and Experience
- # dates_attended = fields.Date('Date Attended')
- # school = fields.Char('School')
- # field_of_study = fields.Char('Field of Study')
- # year_obtained = fields.Selection([(num, str(num)) for num in range(1900, datetime.datetime.now().year + 1)], 'Year')
- # certificate = fields.Char('Certificate')
+ # Education and Experience
+ dates_attended = fields.Date('Date Attended')
+ school = fields.Char('School')
+ field_of_study = fields.Char('Field of Study')
+ year_obtained = fields.Selection([(str(num), str(num)) for num in range(1900, datetime.datetime.now().year + 1)], 'Year')
+ certificate = fields.Char('Certificate')
- # @api.constrains('login')
- # def validate_email(self):
- # if self.login:
- # match = re.match(
- # '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$',
- # self.login)
- # if match is None:
- # raise ValidationError('Not a valid email')
+ assigned_records_count = fields.Integer(string=' Assigned Tasks', compute='_compute_assigned_records_count')
+
+ def _compute_assigned_records_count(self):
+ for record in self:
+ # Count the number of project.task records where this user is in user_ids
+ count = self.env['project.task'].search_count([('user_ids', 'in', record.id)])
+ record.assigned_records_count = count
+
+ @api.constrains('login')
+ def validate_login(self):
+ if self.login:
+ match = re.match(
+ '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$',
+ self.login)
+ if match is None:
+ raise ValidationError('Not a valid email')
diff --git a/local-addons/morons/models/invoice.py b/local-addons/morons/models/invoice.py
new file mode 100644
index 0000000..6111098
--- /dev/null
+++ b/local-addons/morons/models/invoice.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api
+
+class Invoice(models.Model):
+ """
+ A model representing invoices in the MercTrans system.
+
+ This class extends the basic functionality of Odoo's invoicing system
+ to meet the specific requirements of MercTrans. It includes features
+ like a variety of work units, different statuses for invoices and payments,
+ and the capability to handle different currencies with automatic conversion
+ to USD. It also manages the link with purchase orders from projects and
+ calculates payable amounts based on dynamic rates and units.
+
+ Attributes:
+ _name (str): Identifier for the Odoo model.
+ invoice_status_list (list of tuples): A predefined list of possible statuses for an invoice.
+ work_unit_list (list of tuples): A predefined list of work units applicable to an invoice.
+ payment_status_list (list of tuples): A predefined list of payment statuses for tracking invoice payments.
+ issue_date (fields.Date): Field for the date when the invoice is issued.
+ due_date (fields.Date): Field for the date when the invoice payment is due.
+ sender (fields.Char): Field for the name of the sender of the invoice.
+ purchase_order (fields.Many2one): Relationship to the 'project.project' model, representing the associated purchase order.
+ purchase_order_name (fields.Char): Field for displaying the name of the related purchase order, derived from 'purchase_order'.
+ note (fields.Text): Field for any additional notes or comments on the invoice.
+ currency (fields.Many2one): Field linking to the 'res.currency' model for specifying the currency used in the invoice.
+ work_unit (fields.Selection): Field for selecting a work unit from the work_unit_list.
+ rate (fields.Float): Field for the rate applied in the invoice, dependent on the chosen work unit.
+ sale_unit (fields.Integer): Field for the number of work units being billed in the invoice.
+ payable (fields.Monetary): Computed field for the total payable amount in the invoice's currency.
+ payable_usd (fields.Monetary): Computed field for the total payable amount converted to USD.
+ status (fields.Selection): Field for the current status of the invoice, selected from invoice_status_list.
+ payment_status (fields.Selection): Field for the current payment status of the invoice, selected from payment_status_list.
+
+ Methods:
+ _compute_payable_amount(self): Computes the total payable amount based on the rate and sale unit.
+ _compute_amount_usd(self): Converts the payable amount into USD based on the current exchange rate and invoice date.
+ """
+
+ _name = 'morons.invoice'
+
+ invoice_status_list = [
+ ("in progress", "In Progress"),
+ ("completed", "Completed"),
+ ("canceled", "Canceled"),
+ ("draft", "Draft"),
+ ]
+
+ work_unit_list = [
+ ("word", "Word"),
+ ("hour", "Hour"),
+ ("page", "Page"),
+ ("job", "Job"),
+ ]
+
+ payment_status_list = [
+ ("unpaid", "Unpaid"),
+ ("invoiced", "Invoiced"),
+ ("paid", "Paid"),
+ ]
+
+ invoice_id = fields.Char(string='Invoice ID', required=True, copy=False, readonly=True, index=True, default=lambda self: 'New')
+
+ @api.model
+ def create(self, vals):
+ if vals.get('invoice_id', 'New') == 'New':
+ vals['invoice_id'] = self.env['ir.sequence'].next_by_code('morons.invoice') or 'New'
+ result = super(Invoice, self).create(vals)
+ return result
+
+ issue_date = fields.Date(string='Issue Date')
+ due_date = fields.Date(string='Due Date')
+ sender = fields.Many2one('res.user',string='Issued By')
+
+ purchase_order = fields.Many2one('project.task', string='Purchase Order')
+
+ note = fields.Text(string='Note')
+
+ currency = fields.Many2one('res.currency', string='Currency')
+ work_unit = fields.Selection(string='Work Unit', selection=work_unit_list, default='word')
+ rate = fields.Float(string="Rate", digits=(16, 2))
+ sale_unit = fields.Integer(string='Sale Unit')
+ payable = fields.Monetary(string='Payable', currency_field='currency', compute='_compute_payable_amount', store=True, readonly=True)
+ usd_currency_id = fields.Many2one('res.currency', string='USD Currency', default=lambda self: self.env.ref('base.USD'))
+ payable_usd = fields.Monetary(string='Payable(USD)', currency_field='usd_currency_id', compute='_compute_amount_usd')
+
+ @api.depends('rate', 'sale_unit')
+ def _compute_payable_amount(self):
+ for record in self:
+ record.payable = record.rate * record.sale_unit
+
+ @api.depends('payable')
+ def _compute_amount_usd(self):
+ """
+ For now, it is hardcoded to convert from VND to USD and EUR to USD.
+ (TODO) Auto compute regardless of currency.
+ """
+ for record in self:
+ if record.currency.name == 'VND':
+ record.payable_usd = record.payable * 0.000041
+ elif record.currency.name == 'EUR':
+ record.payable_usd = record.payable * 1.10
+ else:
+ record.payable_usd = record.payable
+
+ # @api.depends('payable', 'currency')
+ # def _compute_payable_usd(self):
+ # for record in self:
+ # USD = self.env['res.currency'].search([('name', '=', 'USD')])
+ # curr = self.env['res.currency'].search([('name', '=', 'VND')])
+ # record.payable_usd = USD.compute(record.payable, curr)
+
+ status = fields.Selection(string='Status', selection=invoice_status_list, default='draft')
+ payment_status = fields.Selection(string='Payment Status', selection=payment_status_list, default='unpaid')
+
diff --git a/local-addons/morons/models/project.py b/local-addons/morons/models/project.py
index 785e05b..c277aae 100644
--- a/local-addons/morons/models/project.py
+++ b/local-addons/morons/models/project.py
@@ -28,7 +28,7 @@ class MercTransServices(models.Model):
"""
_name = "merctrans.services"
- _rec_name = "name"
+ _rec_name = "name"
_description = "Services offered by MercTrans"
department_list = [
diff --git a/local-addons/morons/security/security.xml b/local-addons/morons/security/security.xml
index 5ac49d6..337402a 100644
--- a/local-addons/morons/security/security.xml
+++ b/local-addons/morons/security/security.xml
@@ -17,10 +17,19 @@
+
+ BOD
+
+
+
+ Accountants
+
+
+
Contributor access
-
+
@@ -30,7 +39,27 @@
PM access
-
+
+
+
+
+
+
+
+
+
+ Contributor access to invoice
+
+
+
+
+
+
+
+
+
+ PM access to invoice
+
@@ -38,6 +67,26 @@
+
+ BOD access to invoice
+
+
+
+
+
+
+
+
+
+ Accountant access to invoice
+
+
+
+
+
+
+
+
+
+ res.users.form.custom
+ res.users
+
+
+
+
+
+ User Groups
+
+
+
+
+
+
+ Contributor
+ Currency
+ Active
+ Skype
+ Nationality
+ Country of Residence
+ Timezone
+
+
+
+
+
+ Preferred Payment Method
+ Wise ID
+ Bank Account Number
+ Bank Name
+ Bank Address
+
+
+ PayPal ID
+ IBAN
+ SWIFT
+
+
+
+
+
+ Dates Attended
+ School
+ Field of Study
+ Year Obtained
+ Certificate
+
+
+
+
+
+
+
Internal Users
res.users
tree,form
- [('share', '=', False)]
+
+ [('share', '=', False)]
+
+
+
+
+ Morons Invoice
+ morons.invoice
+ INV-
+ 5
+ 1
+
+
+ morons list
+ morons.invoice
+
+
+
+
+
+
+
+
+
+ morons list
+ morons.invoice
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Invoices
+ morons.invoice
+ tree,form
+
+
+
+
+
+
+
+ groups="base.group_system"/>
+
+
+
+
\ No newline at end of file
diff --git a/local-addons/morons/views/user.xml b/local-addons/morons/views/user.xml
index 170b35d..8f7c3fb 100644
--- a/local-addons/morons/views/user.xml
+++ b/local-addons/morons/views/user.xml
@@ -12,8 +12,32 @@
+
Contributor
Currency
+ Active
+ Skype
+ Nationality
+ Country of Residence
+ Timezone
+
+
+ Preferred Payment Method
+ PayPal ID
+ Wise ID
+ Bank Account Number
+ Bank Name
+ IBAN
+ SWIFT
+ Bank Address
+
+
+ Dates Attended
+ School
+ Field of Study
+ Year Obtained
+ Certificate
+