class AccountCode(models.Model): _name = 'budget.opex.account.code' _rec_name = 'account_code' _description = 'Account Code' _inherit = ['mail.thread'] # CHOICES # ---------------------------------------------------------- year_now = fields.Datetime.from_string(fields.Date.today()).year YEARS = [(year, year) for year in range(year_now - 10, year_now + 10)] STATES = choices_tuple(['draft', 'under process', 'authorized', 'closed'], is_sorted=False) # BASIC FIELDS # ---------------------------------------------------------- state = fields.Selection(STATES, default='draft') year = fields.Selection(string='Year', selection=YEARS, default=year_now) account_code = fields.Char(string="Account Code", required=True) start_date = fields.Date(string="Start Date") expenditure_amount = fields.Monetary(currency_field='company_currency_id', string='Expenditure Amount') description = fields.Text(string="Description") grouping = fields.Text(string="Grouping") remarks = fields.Text(string="Remarks") # RELATIONSHIPS # ---------------------------------------------------------- company_currency_id = fields.Many2one('res.currency', readonly=True, default=lambda self: self.env.user.company_id.currency_id) operation_id = fields.Many2one('budget.core.budget', domain=[('is_operation', '=', True), ('state', 'not in', ['draft'])], string='Cost Center' )
class Contract(models.Model): _name = 'budget.contractor.contract' _rec_name = 'contract_ref' _description = 'Contract' # CHOICES # ---------------------------------------------------------- CATEGORIES = choices_tuple( ['consultancy', 'license', 'service', 'supply', 'support', 'turnkey']) STATES = choices_tuple(['active', 'closed'], is_sorted=False) CHANGE_TYPES = choices_tuple(['principal', 'amendment', 'addendum'], is_sorted=False) VERSIONS = [(i, '%d - %s' % (i, int_to_roman(i))) for i in range(1, 100)] SICET_TYPES = choices_tuple(['2a', 'a2/2b', '2b', '3'], is_sorted=False) SUB_SICET_TYPES = choices_tuple(['test1', 'test2', 'test3'], is_sorted=False) SYSTEM_TYPES = choices_tuple(['test1', 'test2', 'test3'], is_sorted=False) NETWORK_TYPES = choices_tuple(['test1', 'test2', 'test3'], is_sorted=False) # BASIC FIELDS # ---------------------------------------------------------- state = fields.Selection(STATES, default='active') contract_no = fields.Char(string="Contract No") sicet_type = fields.Selection(SICET_TYPES) change_type = fields.Selection(CHANGE_TYPES, default='principal') version = fields.Selection(VERSIONS) amount = fields.Monetary(string='Contract Amount', currency_field='company_currency_id') service_amount = fields.Monetary(string='Service Amount', currency_field='company_currency_id') material_amount = fields.Monetary(string='Material Amount', currency_field='company_currency_id') category = fields.Selection(CATEGORIES) # sub_sicet_type = fields.Selection(SUB_SICET_TYPES) # system_type = fields.Selection(SYSTEM_TYPES) # network_type = fields.Selection(NETWORK_TYPES) sign_date = fields.Date(string='Sign Date') commencement_date = fields.Date(string='Commencement Date') end_date = fields.Date(string='End Date') remarks = fields.Text(string='Remarks') # RELATIONSHIPS # ---------------------------------------------------------- company_currency_id = fields.Many2one( 'res.currency', readonly=True, default=lambda self: self.env.user.company_id.currency_id) budget_id = fields.Many2one('budget.core.budget', string='Budget No') contractor_id = fields.Many2one('res.partner', string='Contractor', domain=[('is_budget_contractor', '=', True) ]) section_ids = fields.Many2many('res.partner', 'section_contract_rel', 'contract_id', 'section_id', string="Sections", domain="[('is_budget_section','=',True)]") # COMPUTE FIELDS # ---------------------------------------------------------- contract_ref = fields.Char(string="Contract Reference", compute='_compute_contract_ref', store=True) @api.one @api.depends('contract_no', 'change_type', 'version', 'contractor_id.alias') def _compute_contract_ref(self): change_type = '' if self.change_type == 'principal' and not self.change_type else self.change_type self.contract_ref = "{}/{} {} {}".format( self.contract_no or '', self.contractor_id.alias or '', change_type, self.version or '').upper() # CONSTRAINS FIELDS # ---------------------------------------------------------- _sql_constraints = [ ('contract_ref_uniq', 'unique(contract_ref)', 'Already Exist'), ] # BUTTON ACTIONS / TRANSITIONS # ---------------------------------------------------------- @api.one def set2active(self): self.state = 'active' @api.one def set2close(self): self.state = 'closed'
class Invoice(models.Model): _name = 'budget.invoice.invoice' _rec_name = 'invoice_no' _description = 'Invoice' _order = 'id desc' _inherit = ['mail.thread'] # CHOICES # ---------------------------------------------------------- STATES = choices_tuple([ 'draft', 'verified', 'summary generated', 'under certification', 'sent to finance', 'closed', 'on hold', 'rejected', 'amount hold' ], is_sorted=False) INVOICE_TYPES = choices_tuple([ 'access network', 'supply of materials', 'civil works', 'cable works', 'damage case', 'development', 'fdh uplifting', 'fttm activities', 'maintenance work', 'man power', 'mega project', 'migration', 'on demand activities', 'provisioning', 'recharge', 'recovery' ], is_sorted=False) PAYMENT_TYPES = choices_tuple(['ready for service', 'interim'], is_sorted=False) # BASIC FIELDS # ---------------------------------------------------------- state = fields.Selection(STATES, default='draft') invoice_no = fields.Char(string="Invoice No") invoice_type = fields.Selection(INVOICE_TYPES) payment_type = fields.Selection(PAYMENT_TYPES) revenue_amount = fields.Monetary(string='Revenue Amount', currency_field='company_currency_id') opex_amount = fields.Monetary(string='OPEX Amount', currency_field='company_currency_id') capex_amount = fields.Monetary(string='CAPEX Amount', currency_field='company_currency_id') # TODO TRANSFER PENALTY TO PENALTY AMOUNT penalty = fields.Monetary(string='Penalty Amount', currency_field='company_currency_id') penalty_amount = fields.Monetary(string='Penalty Amount', currency_field='company_currency_id') on_hold_amount = fields.Monetary(string='On Hold Amount', currency_field='company_currency_id') # TODO Make Validation for max 100% on_hold_percentage = fields.Float(string='On Hold Percent (%)', digits=(5, 2)) invoice_date = fields.Date(string='Invoice Date') invoice_cert_date = fields.Date(string='Inv Certification Date') received_date = fields.Date(string='Received Date') signed_date = fields.Date(string='Signed Date') start_date = fields.Date(string='Start Date') end_date = fields.Date(string='End Date') rfs_date = fields.Date(string='RFS Date') reject_date = fields.Date(string='Reject Date') sent_finance_date = fields.Date(string='Sent to Finance Date') closed_date = fields.Date(string='Closed Date') cost_center = fields.Char(string="Cost Center") expense_code = fields.Char(string="Expense Code") remarks = fields.Text(string='Remarks') description = fields.Text(string='Description') # TODO proj_no to pec_no proj_no = fields.Char(string="Project No") pec_no = fields.Char(string="PEC No") # Used for Invoice Summary sequence sequence = fields.Integer('Display order') # RELATIONSHIPS # ---------------------------------------------------------- company_currency_id = fields.Many2one( 'res.currency', readonly=True, default=lambda self: self.env.user.company_id.currency_id) contract_id = fields.Many2one('budget.contractor.contract', string='Contract') task_id = fields.Many2one('budget.capex.task', string='Task') account_code_id = fields.Many2one('budget.opex.account.code', string='Account Code') summary_ids = fields.Many2many('budget.invoice.invoice.summary', 'budget_invoice_summary_invoice', 'invoice_id', 'summary_id', string='Summaries') region_id = fields.Many2one('budget.enduser.region', string="Region") section_id = fields.Many2one('res.partner', string="Section", domain=[('is_budget_section', '=', True)]) sub_section_id = fields.Many2one('res.partner', string="Sub Section", domain=[('is_budget_sub_section', '=', True)]) # RELATED FIELDS # ---------------------------------------------------------- related_contractor_id = fields.Many2one( string='Contractor', related='contract_id.contractor_id', store=True) related_authorized_amount = fields.Monetary( string='Authorized Amount', related='task_id.authorized_amount') related_utilized_amount = fields.Monetary( string='Utilized Amount (IM)', related='task_id.utilized_amount') related_total_amount = fields.Monetary(string='Utilized Amount (FN)', related='task_id.total_amount') # COMPUTE FIELDS # ---------------------------------------------------------- problem = fields.Char(string='Problem', compute='_compute_problem', store=True) invoice_amount = fields.Monetary(string='Invoice Amount', currency_field='company_currency_id', compute='_compute_invoice_amount', store=True) certified_invoice_amount = fields.Monetary( string='Certified Amount', currency_field='company_currency_id', compute='_compute_certified_invoice_amount', inverse='_set_certified_invoice_amount', store=True) @api.one @api.depends('certified_invoice_amount', 'task_id.problem', 'invoice_no') def _compute_problem(self): # Checks Duplicate count = self.env['budget.invoice.invoice'].search_count([ ('invoice_no', '=', self.invoice_no), ('state', '!=', 'rejected') ]) if count > 1: self.problem = 'duplicate' elif self.task_id.problem != 'ok': self.problem = self.task_id.problem # TODO USE CATEGORY ALSO TO IGNORE Y elif self.state == 'draft': if self.task_id.authorized_amount < self.certified_invoice_amount + self.task_id.utilized_amount: self.problem = 'overrun' # TODO MUST BE PLACED IN ACTUALS if self.state == 'draft' and self.task_id.authorized_amount < self.certified_invoice_amount + self.task_id.total_amount: self.problem = 'overrun' else: self.problem = 'ok' @api.one @api.depends('revenue_amount', 'opex_amount', 'capex_amount') def _compute_invoice_amount(self): self.invoice_amount = self.opex_amount + \ self.capex_amount + \ self.revenue_amount @api.one def _set_certified_invoice_amount(self): if self.certified_invoice_amount != self.opex_amount + self.capex_amount + self.revenue_amount: self._compute_certified_invoice_amount() @api.one @api.depends('revenue_amount', 'opex_amount', 'capex_amount', 'penalty_amount', 'on_hold_amount') def _compute_certified_invoice_amount(self): self.certified_invoice_amount = self.opex_amount + \ self.capex_amount + \ self.revenue_amount - \ self.penalty_amount - \ self.on_hold_amount # ONCHANGE # ---------------------------------------------------------- # certified_invoice_amount # on_hold_amount = certified_invoice_amount * on_hold_percentage / 100.00 # on_hold_percentage = on_hold_amount / certified_invoice_amount * 100.00 @api.onchange('on_hold_amount') def onchange_on_hold_percentage(self): total_amount = self.opex_amount + self.capex_amount + self.revenue_amount - self.penalty_amount # if self.on_hold_amount != total_amount * self.on_hold_percentage / 100.00: if total_amount > 0.00: self.on_hold_percentage = self.on_hold_amount / total_amount * 100.00 @api.onchange('on_hold_percentage') def onchange_on_hold_amount(self): total_amount = self.opex_amount + self.capex_amount + self.revenue_amount - self.penalty_amount # if self.on_hold_percentage != self.on_hold_amount / total_amount * 100.00: self.on_hold_amount = total_amount * self.on_hold_percentage / 100.00 # BUTTONS/TRANSITIONS # ---------------------------------------------------------- @api.one def set2draft(self): self.state = 'draft' @api.one def set2verified(self): self.state = 'verified' @api.one def set2summary_generated(self): self.state = 'summary generated' @api.one def set2under_certification(self): self.state = 'under certification' @api.one def set2sent_to_finance(self): self.state = 'sent to finance' @api.one def set2closed(self): self.state = 'closed' @api.one def set2on_hold(self): self.state = 'on hold' @api.one def set2rejected(self): self.state = 'rejected' @api.one def set2amount_hold(self): self.state = 'amount hold'
class Task(models.Model): _name = 'budget.capex.task' _rec_name = 'no' _description = 'Task' _inherit = ['mail.thread'] # CHOICES # ---------------------------------------------------------- year_now = fields.Datetime.from_string(fields.Date.today()).year YEARS = [(year, year) for year in range(year_now - 10, year_now + 10)] STATES = choices_tuple(['draft', 'under process', 'authorized', 'closed'], is_sorted=False) # BASIC FIELDS # ---------------------------------------------------------- state = fields.Selection(STATES, default='draft') category = fields.Char(string="Category") year = fields.Selection(string='Year', selection=YEARS, default=year_now) no = fields.Char(string="Task No", required=True) description = fields.Text(string="Task Description") start_date = fields.Date(string="Task Start Date") expenditure_amount = fields.Monetary(currency_field='company_currency_id', string='Expenditure Amount') commitment_amount = fields.Monetary(currency_field='company_currency_id', string='Commitment Amount') initial_progress = fields.Float(string='Initial Progress') pec_no = fields.Char(string="Pec No") remarks = fields.Text(string="Remarks") # TODO ACTUAL total_amount = fields.Monetary(currency_field='company_currency_id', string='Utilized Amount (FN)') # TODO ACTUAL authorized_amount = fields.Monetary(currency_field='company_currency_id', string='Authorized Amount (FN)') # TODO NEED TO BE REVIEW IF TO BE REMOVE OR NOT gmo_no = fields.Char(string='GMO No') po_no = fields.Char(string='PO No') is_modernization = fields.Boolean(string='Is Modernization') # RELATIONSHIPS # ---------------------------------------------------------- company_currency_id = fields.Many2one('res.currency', readonly=True, default=lambda self: self.env.user.company_id.currency_id) child_ids = fields.One2many('budget.capex.task', 'parent_id', domain=[('is_child', '=', True)], string="Child Tasks") progress_ids = fields.One2many('budget.capex.task.progress', 'id', string="Individual Progress") investment_area_id = fields.Many2one('budget.capex.task.investment.area', string="Investment Area") region_id = fields.Many2one('budget.enduser.region', string="Region") project_id = fields.Many2one('budget.core.budget', domain=[('is_project', '=', True), ('state', 'not in', ['draft'])], string='Project No' ) # COMPUTE FIELDS # ---------------------------------------------------------- total_expenditure_amount = fields.Monetary(compute='_compute_total_expenditure_amount', currency_field='company_currency_id', string='Total Expenditure Amount', store=True) total_commitment_amount = fields.Monetary(compute='_compute_total_commitment_amount', currency_field='company_currency_id', string='Total Commitment Amount', store=True) accrual_amount = fields.Monetary(compute='_compute_accrual_amount', currency_field='company_currency_id', string='Accrual Amount', store=True) progress = fields.Float(compute='_compute_progress', string='Progress', store=True) @api.one @api.depends('child_ids', 'child_ids.expenditure_amount', 'child_ids.state', 'state') def _compute_total_expenditure_amount(self): self.total_expenditure_amount = sum(self.child_ids. \ filtered(lambda r: r.state not in ['draft']). \ mapped('expenditure_amount')) if self.state not in ['draft']: self.total_expenditure_amount += self.expenditure_amount @api.one @api.depends('child_ids', 'child_ids.commitment_amount', 'child_ids.state', 'state') def _compute_total_commitment_amount(self): self.total_commitment_amount = sum(self.child_ids. \ filtered(lambda r: r.state not in ['draft']). \ mapped('commitment_amount')) if self.state not in ['draft']: self.total_commitment_amount += self.commitment_amount @api.one @api.depends('progress_ids', 'progress_ids.accrual_amount') def _compute_accrual_amount(self): self.accrual_amount = sum(self.progress_ids.mapped('accrual_amount')) @api.one @api.depends('progress_ids', 'progress_ids.progress') def _compute_progress(self): self.progress = sum(self.progress_ids.mapped('progress')) # TRANSITIONS # ---------------------------------------------------------- def set2draft(self): self.state = 'draft' def set2under_process(self): self.state = 'under process' def set2authorize(self): self.state = 'authorized' def set2close(self): self.state = 'closed' # CONSTRAINS # ---------------------------------------------------------- _sql_constraints = [ ('uniq_no', 'UNIQUE (no)', 'Task No Must Be unique') ] # OVERRIDE METHODS # ---------------------------------------------------------- @api.model @api.returns('self', lambda rec: rec.id) def create(self, values): if not values.get('progress_ids', False): initial_progress = values.get('initial_progress', 0.00) name = values.get('name', '') start_date = values.get('start_date', False) progress = { 'name': 'INITIAL: %s' % name, 'remarks': 'initial progress', 'progress': initial_progress, 'change_date': start_date, 'is_initial': True } values.update(progress_ids=[(0, 0, progress)]) return super(Task, self).create(values)
class InvoiceSummary(models.Model): _name = 'budget.invoice.invoice.summary' _rec_name = 'summary_no' _description = 'Invoice Summary' _order = 'id desc' _inherit = ['mail.thread'] # CHOICES # ---------------------------------------------------------- STATES = choices_tuple([ 'draft', 'file generated', 'under certification', 'sent to finance', 'closed', 'cancelled' ], is_sorted=False) OBJECTIVES = choices_tuple( ['invoice certification', 'on hold certification']) # BASIC FIELDS # ---------------------------------------------------------- # TODO CONSIDER MAKING SUMMARY NO AS NAME summary_no = fields.Char( string='Summary No', default=lambda self: self._get_default_summary_no()) state = fields.Selection(STATES, default='draft') objective = fields.Selection(OBJECTIVES, default='invoice certification') signed_date = fields.Date(string='Signed Date') closed_date = fields.Date(string='Closed Date') sent_finance_date = fields.Date(string='Sent to Finance Date') sequence = fields.Integer(string='Sequence') # RELATIONSHIPS # ---------------------------------------------------------- # TODO MUST NOT EDITABLE WHEN ON PROCESS, CHECK RULE ACCESS invoice_ids = fields.Many2many('budget.invoice.invoice', 'budget_invoice_summary_invoice', 'summary_id', 'invoice_id') section_id = fields.Many2one('res.partner', string='Section', domain=[('is_budget_section', '=', True)]) # CONSTRAINTS # ---------------------------------------------------------- _sql_constraints = [( '_uniq_summary_no', 'UNIQUE(summary_no)', 'summary must be unqiue!', )] @api.model def _get_default_summary_no(self): from datetime import datetime as dt from dateutil.relativedelta import relativedelta now = dt.utcnow() month = '{:%^b}'.format(now) year = '{:%y}'.format(now) start = dt(now.year, now.month, 1) end = dt(now.year, now.month, 1) + relativedelta(months=1) # end should be the first day next month to make time consider as 23:59:99 domain = [('create_date', '>=', fields.Datetime.to_string(start)), ('create_date', '<', fields.Datetime.to_string(end))] summaries = self.search(domain) if len(summaries) == 0: sr = 1 else: sr = summaries[0].summary_no.split('-')[-1] sr = int(sr) + 1 return 'IM-%s%s-%03d' % (month, year, sr) @api.one @api.returns('self', lambda value: value.id) def copy(self, default=None): default = dict(default or {}) default.update(summary_no=self._get_default_summary_no()) return super(InvoiceSummary, self).copy(default) @api.one def generate_file(self): """ generate/create a invoice summary set STATE to GENERATED """ from openpyxl.drawing.image import Image from ..xlsx_creator.creator import Creator import tempfile, shutil # OPEN FORM TEMPLATE creator = Creator(section=self.section_id.alias) wb, logo_img, signature_img = creator.get_context() # WORK SHEET MAIN # ---------------------------------------------------------- row = 13 column = 1 sr = 1 signature_coor = "B18" # B18 logo_coor = "J1" # J1 ws = wb.get_sheet_by_name('main') ws.cell("C4").value = self.summary_no ws.cell("C5").value = fields.Datetime.from_string( self.create_date).strftime('%d-%b-%Y') # Create Table ws.insert_rows(row, len(self.invoice_ids) - 1) #No, Reg, Contractor, Invoice No, Contract, Revenue, OpEx, CapEx, Total Amt, Budget/Yr. #1 , 2 , 3, , 4 , 5 6 , 7 , 8 , 9 , 10 , 11 for r in self.invoice_ids.sorted(key=lambda r: r.sequence): ws.cell(row=row, column=column).value = sr ws.cell(row=row, column=column + 1).value = r.region_id.alias.upper() or '' ws.cell(row=row, column=column + 2).value = r.contract_id.contractor_id.name or '' ws.cell(row=row, column=column + 3).value = r.invoice_no or '' ws.cell(row=row, column=column + 4).value = r.contract_id.contract_no or '' ws.cell(row=row, column=column + 5).value = r.revenue_amount or '' ws.cell(row=row, column=column + 6).value = r.opex_amount or '' ws.cell(row=row, column=column + 7).value = r.capex_amount or '' ws.cell(row=row, column=column + 8).value = r.on_hold_percentage / 100 or '' ws.cell(row=row, column=column + 9).value = r.certified_invoice_amount or '' ws.cell(row=row, column=column + 10).value = r.task_id.no or fields.Datetime.from_string( r.rfs_date).strftime('%d-%b-%Y') row += 1 sr += 1 # INSERT HEADER LOGO AND SIGNATURE logo = Image(logo_img) signature = Image(signature_img) ws.add_image(logo, logo_coor) ws.add_image( signature, "%s" % signature_coor[0] + str(int(signature_coor[1:]) + sr)) temp_dir = tempfile.mkdtemp() temp_file = os.path.join(temp_dir, '%s.xlsx' % self.summary_no) wb.save(temp_file) # Attach generated document to filestore ir_attach = self.env['ir.attachment'] full_path = os.path.join(temp_file) with open(full_path, 'r') as fp: data = fp.read().encode('base64') filename = os.path.split(full_path)[1] values = dict( name=filename, datas_fname=filename, res_id=self.id, res_model='budget.invoice.invoice.summary', type='binary', datas=data, ) ir_attach.create(values) # Clear shutil.rmtree(temp_dir) # ONCHANGE # ---------------------------------------------------------- @api.onchange('objective') def _check_objective(self): if self.objective == 'invoice certification': return {'domain': {'invoice_ids': [('state', '=', 'verified')]}} elif self.objective == 'on hold certification': return {'domain': {'invoice_ids': [('state', '=', 'amount hold')]}} # BUTTONS/TRANSITIONS # ---------------------------------------------------------- # TODO MUST DO A TRANSITION FOR RETURNING TO DRAFT @api.one def set2draft(self): self.state = 'draft' @api.one def set2file_generated(self): if not self.section_id: raise ValidationError('Section is required') elif len(self.invoice_ids) == 0: raise ValidationError('Empty Invoice List') self.generate_file() # Set related invoices state to "summary generated" for invoice in self.invoice_ids: invoice.signal_workflow('summary_generate') self.state = 'file generated' @api.one def set2under_certification(self): for invoice in self.invoice_ids: invoice.signal_workflow('certify') self.state = 'under certification' @api.one def set2sent_to_finance(self): if self.signed_date is False: raise ValidationError('Signed Date is Required') elif self.sent_finance_date is False: raise ValidationError('Sent to Finance Date is Required') for invoice in self.invoice_ids: invoice.sent_finance_date = self.sent_finance_date invoice.signed_date = self.signed_date invoice.signal_workflow('send_to_finance') self.state = 'sent to finance' @api.one def set2closed(self): if self.closed_date is False: raise ValidationError('Close Date is Required') for invoice in self.invoice_ids: invoice.closed_date = self.closed_date if invoice.on_hold_amount > 0 and invoice.state != 'amount hold': invoice.signal_workflow('amount_hold') else: invoice.signal_workflow('close') self.state = 'closed' @api.one def set2cancelled(self): for invoice in self.invoice_ids: invoice.signal_workflow('cancel') self.state = 'cancelled'