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 +