Skip to content

Commit

Permalink
Merge pull request #10 from Merctrans/feature/invoice
Browse files Browse the repository at this point in the history
Feature/invoice
  • Loading branch information
svseas authored Dec 25, 2023
2 parents e6222fd + 688c6f8 commit 9a2ecb0
Show file tree
Hide file tree
Showing 9 changed files with 399 additions and 54 deletions.
4 changes: 2 additions & 2 deletions local-addons/morons/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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': [
Expand Down
3 changes: 2 additions & 1 deletion local-addons/morons/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-

from . import project
from . import contributor
from . import contributor
from . import invoice
94 changes: 51 additions & 43 deletions local-addons/morons/models/contributor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
116 changes: 116 additions & 0 deletions local-addons/morons/models/invoice.py
Original file line number Diff line number Diff line change
@@ -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')

2 changes: 1 addition & 1 deletion local-addons/morons/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class MercTransServices(models.Model):
"""

_name = "merctrans.services"
_rec_name = "name"
_rec_name = "name"
_description = "Services offered by MercTrans"

department_list = [
Expand Down
57 changes: 53 additions & 4 deletions local-addons/morons/security/security.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@
<field name="category_id" ref="module_category_morons"/>
</record>

<record id="group_bod" model="res.groups">
<field name="name">BOD</field>
<field name="category_id" ref="module_category_morons"/>
</record>
<record id="group_accountants" model="res.groups">
<field name="name">Accountants</field>
<field name="category_id" ref="module_category_morons"/>
</record>

<!-- Create CRUD rights -->
<record id="module_category_contributor_access" model="ir.model.access">
<field name="name">Contributor access</field>
<field name="model_id" ref="morons.model_project_task"/>
<field name="model_id" ref="base.module_project"/>
<field name="group_id" ref="morons.group_contributors"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="0"/>
Expand All @@ -30,14 +39,54 @@

<record id="module_category_pm_access" model="ir.model.access">
<field name="name">PM access</field>
<field name="model_id" ref="morons.model_project_task"/>
<field name="model_id" ref="base.module_project"/>
<field name="group_id" ref="morons.group_pm"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>

<record id="module_category_pm_access_invoice" model="ir.model.access">
<field name="name">Contributor access to invoice</field>
<field name="model_id" ref="morons.model_morons_invoice"/>
<field name="group_id" ref="morons.group_contributors"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>

<record id="module_category_contributor_access_invoice" model="ir.model.access">
<field name="name">PM access to invoice</field>
<field name="model_id" ref="morons.model_morons_invoice"/>
<field name="group_id" ref="morons.group_pm"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>

<record id="module_category_bod_access_invoice" model="ir.model.access">
<field name="name">BOD access to invoice</field>
<field name="model_id" ref="morons.model_morons_invoice"/>
<field name="group_id" ref="morons.group_bod"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>

<record id="module_category_accountant_access_invoice" model="ir.model.access">
<field name="name">Accountant access to invoice</field>
<field name="model_id" ref="morons.model_morons_invoice"/>
<field name="group_id" ref="morons.group_accountants"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>

<!-- Rule for normal users -->
<!-- <record id="rule_supreme_court_letter_own" model="ir.rule">
<field name="name">Supreme Court Letter Own</field>
Expand All @@ -48,15 +97,15 @@

<record model="ir.rule" id="rule_po_contributor_access">
<field name="name">PO Contributor Access</field>
<field name="model_id" ref="morons.model_project_task"/>
<field name="model_id" ref="base.module_project"/>
<field name="domain_force">[('user_id','=',user.id)]</field>
<field name="groups" eval="[( 4, ref('morons.group_contributors'))]"/>
</record>


<record model="ir.rule" id="rule_po_pm_access">
<field name="name">PO PM Access</field>
<field name="model_id" ref="morons.model_project_task"/>
<field name="model_id" ref="base.module_project"/>
<field name="domain_force">[('user_id','=',user.id)]</field>
<field name="groups" eval="[(4, ref('morons.group_pm'))]"/>
</record>
Expand Down
Empty file.
Loading

0 comments on commit 9a2ecb0

Please sign in to comment.