class wizard_move_to_hanged_warehouse(models.TransientModel): _name = "wizard_move_to_hanged_warehouse" @api.model def get_stock_move_to_hanged_warehouse_lines(self): stock_active_ids = self._context.get('active_ids') stock_ids = self.env['stock.quant'].browse(stock_active_ids) dict = [] for stk in stock_ids: for line in stk: res = self.env['stock.quant.items'].create({ 'product_id': line.product_id.id, 'quantity': line.quantity, 'quantity_moved': line.quantity, 'location_id': line.location_id.id, 'lot_id': line.lot_id.id, }) dict.append(res.id) return [(6, 0, dict)] # here the action to get data from the wizard showed in list view and create stock picking def do_transfer_selected_lines(self): active_record_id = self._context.get('active_ids')[0] active_obj_location = self.env['stock.quant'].browse(active_record_id) for edata in self.stk_quant_ids: if edata.quantity_moved == 0 or edata.quantity_moved > edata.quantity: raise Warning("you can not make stock picking with this quantity !") if edata.location_id != active_obj_location.location_id: raise Warning("you can't make this action by selecting lines of different locations") self.sequence = self.env['ir.sequence'].get('move_to_hanged_warehouse_number') operation = self.env['operation.operation'].search([('location_id', '=', self.location_id.id)]) vals = { 'name':self.location_id.name + self.sequence, 'partner_id': self.partner_id.id, 'picking_type_id': self.picking_type_id.id, 'location_id': self.location_id.id, 'location_dest_id': self.location_dest_id.id, 'origin': self.location_id.name, 'operation_id': operation.id, } move_lines = [] scan_product_ids_lst = [] data_line = [] for component in self.stk_quant_ids: line = [0, 0, {'product_id': component.product_id.id, 'product_uom': component.product_id.uom_id.id, 'product_uom_qty': component.quantity_moved, 'name': component.product_id.name }] if self.scan_option == 'withoutscan' and (component.product_id.tracking == 'lot' or component.product_id.tracking == 'serial'): scan_product_ids_lst.append([0, 0, { 'product_id': component.product_id.id, 'product_uom_qty': component.quantity_moved, 'lot_no': component.lot_id.name, } ]) if component.product_id.tracking == 'lot' or component.product_id.tracking == 'serial': data_line.append(component.lot_id.id) if component.quantity != 0.0: move_lines.append(line) vals['move_lines'] = move_lines vals['scan_products_ids'] = scan_product_ids_lst if len(move_lines) == 0: raise Warning(str("you can not make Stock Picking with no lines")) res = self.env['stock.picking'].create(vals) res.action_confirm() return { "type": "ir.actions.act_window", "res_model": "stock.picking", "views": [[False, "form"]], "res_id": res.id, "target": "current", } # return res.action_done() # get default location @api.model def _get_location_name(self): active_record = self._context.get('active_ids')[0] active_obj = self.env['stock.quant'].browse(active_record) return active_obj.location_id # get default logged in user def _default_partner(self): active_record = self._context.get('active_ids')[0] active_obj = self.env['stock.quant'].browse(active_record) location_id = active_obj.location_id partner = self.env['res.partner'].search([('operations_location', '=', location_id.location_id.id)], limit=1) return partner # get picking type def _get_picking_type(self): active_record = self._context.get('active_ids')[0] active_obj = self.env['stock.quant'].browse(active_record) warehouse_id = active_obj.location_id.warehouse_id # print "picking "+str(warehouse_id.name) picking_type = self.env['stock.picking.type'].search( [('code', '=', 'internal'), ('warehouse_id', '=', warehouse_id.id), ('surgeries_supply', '=', True)]) return picking_type # get_location_dest_id def _get_location_dest_id(self): active_record = self._context.get('active_ids')[0] active_obj = self.env['stock.quant'].browse(active_record) dist_warehouse = self.env['stock.warehouse'].search([('is_hanged_warehouse','=',True)]) location = self.env['stock.location'].search([('warehouse_id','=',dist_warehouse.id),('is_operation_location','=',True),('usage','=','internal')]) return location location_id = fields.Many2one('stock.location', string="Location", default=_get_location_name, readonly=True) location_dest_id = fields.Many2one('stock.location', string="Destination Location", default=_get_location_dest_id, required=True) picking_type_id = fields.Many2one('stock.picking.type', string="Picking Type", required=True,default=_get_picking_type,) stk_quant_ids = fields.One2many('stock.quant.items', 'wizard_id_hanged', string="Stock Quant Items", required=False, default=get_stock_move_to_hanged_warehouse_lines) name = fields.Char(string="Name") partner_id = fields.Many2one('res.partner', string="Partner",default=_default_partner) warehouse_id = fields.Many2one(related="location_id.warehouse_id",store=True,readonly=True,string="Warehouse") sequence = fields.Char(string="Request Reference", required=True, readonly=True, select=True, copy=False, default='New') scan_option = fields.Selection([('withscan', 'With scan'), ('withoutscan', 'Without Scan')], required=True)
class ka_account_payment_export_faktur_pajak_wiz(models.TransientModel): _name = "ka_account.payment.export.faktur.pajak.wiz" text1 = fields.Char( 'Name', default='Pilih jenis pajak, Kemudian Klik Export Pajak') text2 = fields.Char('Name', default='Download Link') state_x = fields.Selection([("choose", "Choose"), ("get", "Get")], string="State X", default="choose", copy=False) export = fields.Selection([("ppn", "PPN"), ("pph", "PPh")], string="Jenis Pajak", default="ppn") data_x = fields.Binary('File', readonly=True) name = fields.Char('Filename', readonly=True) no_urut = fields.Integer(string="No urut") def find_between(self, s, first, last): try: start = s.index(first) + len(first) end = s.index(last, start) return s[start:end] except ValueError: return "" @api.multi def create_efaktur(self): context = dict(self._context or {}) data = {} active_ids = self.env.context.get('active_ids') delimiter = ',' data['form'] = active_ids user = self.env.user tz = pytz.timezone(user.tz) if user.tz else pytz.utc now = pytz.utc.localize(datetime.now()).astimezone(tz) download_time = datetime.strftime(now, "%d-%m-%Y_%H:%M") if self.export == "ppn": filename = "faktur_pajak_" + download_time + ".csv" output_head = '"FM"' + delimiter + '"KD_JENIS_TRANSAKSI"' + delimiter + '"FG_PENGGANTI"' + delimiter + '"NOMOR_FAKTUR"' + delimiter + '"MASA_PAJAK"' + delimiter + '"TAHUN_PAJAK"' + delimiter + "TANGGAL_FAKTUR" + delimiter output_head += '"NPWP"' + delimiter + '"NAMA"' + delimiter + '"ALAMAT_LENGKAP"' + delimiter + '"JUMLAH_DPP"' + delimiter + '"JUMLAH_PPN"' + delimiter + '"JUMLAH_PPNBM"' + delimiter + '"IS_CREDITABLE"' + '\n' for p in self.env['ka_account.payment'].browse(data['form']): if p.efaktur_url is False: raise UserError(_("Please Fill E-Faktur URL")) else: barcode = urllib2.urlopen(p.efaktur_url).read() if barcode == '' or not barcode: return kdJenisTransaksi = self.find_between( barcode, "<kdJenisTransaksi>", "</kdJenisTransaksi>") fgPengganti = self.find_between(barcode, "<fgPengganti>", "</fgPengganti>") nomorFaktur = self.find_between(barcode, "<nomorFaktur>", "</nomorFaktur>") tanggalFaktur = datetime.strftime( datetime.strptime( self.find_between(barcode, "<tanggalFaktur>", "</tanggalFaktur>"), "%d/%m/%Y"), "%Y-%m-%d") npwpPenjual = self.find_between(barcode, "<npwpPenjual>", "</npwpPenjual>") namaPenjual = self.find_between(barcode, "<namaPenjual>", "</namaPenjual>") alamatPenjual = self.find_between(barcode, "<alamatPenjual>", "</alamatPenjual>") jumlahDpp = self.find_between(barcode, "<jumlahDpp>", "</jumlahDpp>") jumlahPpn = self.find_between(barcode, "<jumlahPpn>", "</jumlahPpn>") jumlahPpnBm = self.find_between(barcode, "<jumlahPpnBm>", "</jumlahPpnBm>") output_head += '"FM"' + delimiter + '"' + kdJenisTransaksi + '"' + delimiter + '"' + fgPengganti + '"' + delimiter + '"' + nomorFaktur + '"' + delimiter output_head += '"' + datetime.strftime( now, "%m" ) + '"' + delimiter + '"' + datetime.strftime( now, "%Y" ) + '"' + delimiter + '"' + tanggalFaktur + '"' + delimiter + '"' + npwpPenjual + '"' + delimiter output_head += '"' + namaPenjual + '"' + delimiter + '"' + alamatPenjual + '"' + delimiter + '"' + str( jumlahDpp) + '"' + delimiter + '"' + str( jumlahPpn) + '"' + delimiter output_head += '"' + jumlahPpnBm + '"' + delimiter + '"1"' + '\n' elif self.export == "pph": filename = "bukti_tagihan_pph_" + download_time + ".csv" output_head = '' for p in self.env['ka_account.payment'].browse(data['form']): if p.state != 'paid': raise UserError(_("Status Tagihan Belum Dibayar!")) else: tgl_bayar = p.date_paid[-2:] + "/" + p.date_paid[ 5:-3] + "/" + p.date_paid[:4] output_head += '"F113304"' + delimiter + '"' + p.date_paid[ 5: -3] + '"' + delimiter + '"' + p.date_paid[:4] + '"' + delimiter + '"0"' + delimiter + '"' + str( p.no_npwp ) + '"' + delimiter + '"' + p.partner_id.name + '"' + delimiter output_head += '"' + p.partner_id.street + '"' + delimiter + '"' + str( self.no_urut ) + '"' + delimiter + '"' + tgl_bayar + '"' + delimiter output_head += '"0"' + delimiter + '"0,25"' + delimiter + '"0"' + delimiter output_head += '"0"' + delimiter + '"0,1"' + delimiter + '"0"' + delimiter output_head += '"0"' + delimiter + '"0,3"' + delimiter + '"0"' + delimiter output_head += '"0"' + delimiter + '"0,45"' + delimiter + '"0"' + delimiter output_head += '"Farmasi"' + delimiter + '"0"' + delimiter + '"0,25"' + delimiter + '"0"' + delimiter output_head += '""' + delimiter output_head += '"0"' + delimiter + '"0"' + delimiter + '"0"' + delimiter output_head += '""' + delimiter output_head += '"0"' + delimiter + '"0"' + delimiter + '"0"' + delimiter output_head += '"PERKEBUNAN"' + delimiter + '"' + str( int(p.amount_dpp) ) + '"' + delimiter + '"0,25"' + delimiter + '"' + str( int(p.amount_pph)) + '"' + delimiter output_head += '""' + delimiter output_head += '"0"' + delimiter + '"0,25"' + delimiter + '"0"' + delimiter output_head += '""' + delimiter output_head += '"0"' + delimiter + '"0"' + delimiter + '"0"' + delimiter output_head += '""' + delimiter output_head += '"0"' + delimiter + '"0"' + delimiter + '"0"' + delimiter output_head += '"' + str(int( p.amount_dpp)) + '"' + delimiter + '"' + str( int(p.amount_pph)) + '"' + '\n' self.no_urut += 1 my_utf8 = output_head.encode("utf-8") out = base64.b64encode(my_utf8) self.write({'state_x': 'get', 'data_x': out, 'name': filename}) ir_model_data = self.env['ir.model.data'] form_res = ir_model_data.get_object_reference( 'ka_account', 'ka_account_payment_export_faktur_pajak_form' ) #module 'pti_faktur' and 'id wizard form' form_id = form_res and form_res[1] or False return { 'name': _('Download csv'), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'ka_account.payment.export.faktur.pajak.wiz', #model wizard 'res_id': self.id, #id wizard 'view_id': False, 'views': [(form_id, 'form')], 'type': 'ir.actions.act_window', 'target': 'current' }
class SyncTask(models.Model): _name = "sync.task" _description = "Sync Task" project_id = fields.Many2one("sync.project", ondelete="cascade") name = fields.Char("Name", help="e.g. Sync Products", required=True) code = fields.Text("Code") code_check = fields.Text("Syntax check", store=False, readonly=True) active = fields.Boolean(default=True) cron_ids = fields.One2many("sync.trigger.cron", "sync_task_id", copy=True) automation_ids = fields.One2many( "sync.trigger.automation", "sync_task_id", copy=True ) webhook_ids = fields.One2many("sync.trigger.webhook", "sync_task_id", copy=True) button_ids = fields.One2many( "sync.trigger.button", "sync_task_id", string="Manual Triggers", copy=True ) active_cron_ids = fields.Many2many( "sync.trigger.cron", string="Enabled Crons", compute="_compute_active_triggers", context={"active_test": False}, ) active_automation_ids = fields.Many2many( "sync.trigger.automation", string="Enabled DB Triggers", compute="_compute_active_triggers", context={"active_test": False}, ) active_webhook_ids = fields.Many2many( "sync.trigger.webhook", string="Enabled Webhooks", compute="_compute_active_triggers", context={"active_test": False}, ) active_button_ids = fields.Many2many( "sync.trigger.button", string="Enabled Buttons", compute="_compute_active_triggers", context={"active_test": False}, ) job_ids = fields.One2many("sync.job", "task_id") job_count = fields.Integer(compute="_compute_job_count") log_ids = fields.One2many("ir.logging", "sync_task_id") log_count = fields.Integer(compute="_compute_log_count") @api.depends("job_ids") def _compute_job_count(self): for r in self: r.job_count = len(r.job_ids) @api.depends("log_ids") def _compute_log_count(self): for r in self: r.log_count = len(r.log_ids) @api.constrains("code") def _check_python_code(self): for r in self.sudo().filtered("code"): msg = test_python_expr(expr=r.code, mode="exec") if msg: raise ValidationError(msg) @api.onchange("code") def onchange_code(self): for r in self: if not r.code: continue msg = test_python_expr(expr=r.code, mode="exec") r.code_check = msg @api.depends( "cron_ids.active", "automation_ids.active", "webhook_ids.active", "button_ids.active", ) def _compute_active_triggers(self): for r in self.with_context(active_test=False): r.active_cron_ids = r.with_context(active_test=True).cron_ids r.active_automation_ids = r.with_context(active_test=True).automation_ids r.active_webhook_ids = r.with_context(active_test=True).webhook_ids r.active_button_ids = r.with_context(active_test=True).button_ids def start( self, trigger, args=None, with_delay=False, force=False, raise_on_error=True ): self.ensure_one() if not force and not (self.active and self.project_id.active): _logger.info( "Triggering archived project or task: %s", trigger.trigger_name ) return None job = self.env["sync.job"].create_trigger_job(trigger) run = self.with_delay().run if with_delay else self.run if not with_delay and self.env.context.get("new_cursor_logs") is not False: # log records are created via new cursor and they use job.id value for sync_job_id field self.env.cr.commit() # pylint: disable=invalid-commit queue_job_or_result = run( job, trigger._sync_handler, args, raise_on_error=raise_on_error ) if with_delay and not self.env.context.get("test_queue_job_no_delay"): job.queue_job_id = queue_job_or_result.db_record() return job else: return job, queue_job_or_result def run(self, job, function, args=None, kwargs=None, raise_on_error=True): log = self.project_id._get_log_function(job, function) try: eval_context = self.project_id._get_eval_context(job, log) code = self.code start_time = time.time() result = self._eval(code, function, args, kwargs, eval_context) log( "Executing {}: {:05.3f} sec".format(function, time.time() - start_time), LOG_DEBUG, ) log("Job finished") return result, log except Exception: buff = StringIO() traceback.print_exc(file=buff) log(buff.getvalue(), LOG_CRITICAL) if raise_on_error: raise @api.model def _eval(self, code, function, args, kwargs, eval_context): ARGS = "EXECUTION_ARGS_" KWARGS = "EXECUTION_KWARGS_" RESULT = "EXECUTION_RESULT_" code += """ {RESULT} = {function}(*{ARGS}, **{KWARGS}) """.format( RESULT=RESULT, function=function, ARGS=ARGS, KWARGS=KWARGS ) eval_context[ARGS] = args or () eval_context[KWARGS] = kwargs or {} safe_eval( code, eval_context, mode="exec", nocopy=True ) # nocopy allows to return RESULT return eval_context[RESULT] def name_get(self): if not self.env.context.get("name_with_project"): return super(SyncTask, self).name_get() result = [] for r in self: name = r.project_id.name + ": " + r.name result.append((r.id, name)) return result def unlink(self): self.with_context(active_test=False).mapped("cron_ids").unlink() self.with_context(active_test=False).mapped("automation_ids").unlink() self.with_context(active_test=False).mapped("webhook_ids").unlink() return super(SyncTask, self).unlink()
class sales_channel_instance(models.Model): """ For instance of Sales Channel""" _name = 'sales.channel.instance' def _get_installed_module(self): sel_obj = self.env['ir.module.module'] sele_ids = sel_obj.search([('name', 'in', ['magento_2_v10', 'magento_odoo_v10', 'ebay_base_ecommerce_merge', 'amazon_odoo_v11', 'woocommerce_odoo', 'virtuemart_odoo']), ('state', '=', 'installed')]) print("**********sele_ids**************", sele_ids) select = [] for s in sele_ids: select.append((str(s.name), s.shortdesc)) print("#########select######################", select) return select name = fields.Char(string='Name', size=64, required=True) module_id = fields.Selection(_get_installed_module, string='Module', size=100) # image = fields.Binary(compute='_get_default_image') image = fields.Image(string="Image", max_width=64, max_height=64, compute='_get_default_image') @api.model def get_module_id(self, module_id): return {'value': {'m_id': module_id}} @api.model def _get_default_image(self): # if partner_type in ['other'] and parent_id: # parent_image = self.browse(parent_id).image # image = parent_image and parent_image.decode('base64') or None image_path, colorize = False, False if self.module_id == 'amazon_odoo_v11': image_path = get_module_resource('ebay_base_ecommerce_merge', 'static/images', 'amazon_logo.png') if self.module_id == 'ebay_base_ecommerce_merge': image_path = get_module_resource('ebay_base_ecommerce_merge', 'static/images', 'EBay_logo.png') if self.module_id == 'magento_odoo_v10': image_path = get_module_resource('ebay_base_ecommerce_merge', 'static/images', 'logomagento.png') if self.module_id == 'shopify_odoo_v10': image_path = get_module_resource('ebay_base_ecommerce_merge', 'static/images', 'shopify.png') # if image_path: # with open(image_path, 'rb') as f: # image = f.read() # if image_path: # f = open(image_path, 'rb') # image = f.read() # image=Image.open(image_path) # if image and colorize: # image = tools.image_colorize(image) # self.image = tools.image_resize_image_big(image.encode('base64')) # self.image = base64.b64encode(image).decode('ascii') self.image = base64.b64encode(open(image_path, 'rb').read()) def create_stores(self): """ For create store of Sales Channel """ (instances,) = self shop_obj = self.env['sale.shop'] shop_ids = shop_obj.search([('instance_id', '=', self[0].id)]) payment_ids = self.env['account.payment.term'].search([]) if not shop_ids: shop_data = { 'sale_channel_shop': True, 'name': instances.name + ' Shop', 'payment_default_id': payment_ids[0].id, 'warehouse_id': 1, 'instance_id': self[0].id, 'marketplace_image': instances.image, 'order_policy': 'prepaid' } shop_id = shop_obj.create(shop_data) else: shop_id = shop_ids[0] return shop_id
class asset_category(models.Model): _description = 'Asset Tags' _name = 'asset.category' name = fields.Char('Tag', required=True, translate=True) asset_ids = fields.Many2many('asset.asset', id1='category_id', id2='asset_id', string='Assets')
class Challenge(models.Model): """Gamification challenge Set of predifined objectives assigned to people with rules for recurrence and rewards If 'user_ids' is defined and 'period' is different than 'one', the set will be assigned to the users for each period (eg: every 1st of each month if 'monthly' is selected) """ _name = 'gamification.challenge' _description = 'Gamification Challenge' _inherit = 'mail.thread' _order = 'end_date, start_date, name, id' name = fields.Char("Challenge Name", required=True, translate=True) description = fields.Text("Description", translate=True) state = fields.Selection([ ('draft', "Draft"), ('inprogress', "In Progress"), ('done', "Done"), ], default='draft', copy=False, string="State", required=True, track_visibility='onchange') manager_id = fields.Many2one( 'res.users', default=lambda self: self.env.uid, string="Responsible", help="The user responsible for the challenge.",) user_ids = fields.Many2many('res.users', 'gamification_challenge_users_rel', string="Users", help="List of users participating to the challenge") user_domain = fields.Char("User domain", help="Alternative to a list of users") period = fields.Selection([ ('once', "Non recurring"), ('daily', "Daily"), ('weekly', "Weekly"), ('monthly', "Monthly"), ('yearly', "Yearly") ], default='once', string="Periodicity", help="Period of automatic goal assigment. If none is selected, should be launched manually.", required=True) start_date = fields.Date("Start Date", help="The day a new challenge will be automatically started. If no periodicity is set, will use this date as the goal start date.") end_date = fields.Date("End Date", help="The day a new challenge will be automatically closed. If no periodicity is set, will use this date as the goal end date.") invited_user_ids = fields.Many2many('res.users', 'gamification_invited_user_ids_rel', string="Suggest to users") line_ids = fields.One2many('gamification.challenge.line', 'challenge_id', string="Lines", help="List of goals that will be set", required=True, copy=True) reward_id = fields.Many2one('gamification.badge', string="For Every Succeeding User") reward_first_id = fields.Many2one('gamification.badge', string="For 1st user") reward_second_id = fields.Many2one('gamification.badge', string="For 2nd user") reward_third_id = fields.Many2one('gamification.badge', string="For 3rd user") reward_failure = fields.Boolean("Reward Bests if not Succeeded?") reward_realtime = fields.Boolean("Reward as soon as every goal is reached", default=True, help="With this option enabled, a user can receive a badge only once. The top 3 badges are still rewarded only at the end of the challenge.") visibility_mode = fields.Selection([ ('personal', "Individual Goals"), ('ranking', "Leader Board (Group Ranking)"), ], default='personal', string="Display Mode", required=True) report_message_frequency = fields.Selection([ ('never', "Never"), ('onchange', "On change"), ('daily', "Daily"), ('weekly', "Weekly"), ('monthly', "Monthly"), ('yearly', "Yearly") ], default='never', string="Report Frequency", required=True) report_message_group_id = fields.Many2one('mail.channel', string="Send a copy to", help="Group that will receive a copy of the report in addition to the user") report_template_id = fields.Many2one('mail.template', default=lambda self: self._get_report_template(), string="Report Template", required=True) remind_update_delay = fields.Integer("Non-updated manual goals will be reminded after", help="Never reminded if no value or zero is specified.") last_report_date = fields.Date("Last Report Date", default=fields.Date.today) next_report_date = fields.Date("Next Report Date", compute='_get_next_report_date', store=True) category = fields.Selection([ ('hr', 'Human Resources / Engagement'), ('other', 'Settings / Gamification Tools'), ], string="Appears in", required=True, default='hr', help="Define the visibility of the challenge through menus") REPORT_OFFSETS = { 'daily': timedelta(days=1), 'weekly': timedelta(days=7), 'monthly': relativedelta(months=1), 'yearly': relativedelta(years=1), } @api.depends('last_report_date', 'report_message_frequency') def _get_next_report_date(self): """ Return the next report date based on the last report date and report period. """ for challenge in self: last = challenge.last_report_date offset = self.REPORT_OFFSETS.get(challenge.report_message_frequency) if offset: challenge.next_report_date = last + offset else: challenge.next_report_date = False def _get_report_template(self): template = self.env.ref('gamification.simple_report_template', raise_if_not_found=False) return template.id if template else False @api.model def create(self, vals): """Overwrite the create method to add the user of groups""" if vals.get('user_domain'): users = self._get_challenger_users(ustr(vals.get('user_domain'))) if not vals.get('user_ids'): vals['user_ids'] = [] vals['user_ids'].extend((4, user.id) for user in users) return super(Challenge, self).create(vals) @api.multi def write(self, vals): if vals.get('user_domain'): users = self._get_challenger_users(ustr(vals.get('user_domain'))) if not vals.get('user_ids'): vals['user_ids'] = [] vals['user_ids'].extend((4, user.id) for user in users) write_res = super(Challenge, self).write(vals) if vals.get('report_message_frequency', 'never') != 'never': # _recompute_challenge_users do not set users for challenges with no reports, subscribing them now for challenge in self: challenge.message_subscribe([user.partner_id.id for user in challenge.user_ids]) if vals.get('state') == 'inprogress': self._recompute_challenge_users() self._generate_goals_from_challenge() elif vals.get('state') == 'done': self._check_challenge_reward(force=True) elif vals.get('state') == 'draft': # resetting progress if self.env['gamification.goal'].search([('challenge_id', 'in', self.ids), ('state', '=', 'inprogress')], limit=1): raise exceptions.UserError(_("You can not reset a challenge with unfinished goals.")) return write_res ##### Update ##### @api.model # FIXME: check how cron functions are called to see if decorator necessary def _cron_update(self, ids=False, commit=True): """Daily cron check. - Start planned challenges (in draft and with start_date = today) - Create the missing goals (eg: modified the challenge to add lines) - Update every running challenge """ # start scheduled challenges planned_challenges = self.search([ ('state', '=', 'draft'), ('start_date', '<=', fields.Date.today()) ]) if planned_challenges: planned_challenges.write({'state': 'inprogress'}) # close scheduled challenges scheduled_challenges = self.search([ ('state', '=', 'inprogress'), ('end_date', '<', fields.Date.today()) ]) if scheduled_challenges: scheduled_challenges.write({'state': 'done'}) records = self.browse(ids) if ids else self.search([('state', '=', 'inprogress')]) # in cron mode, will do intermediate commits # FIXME: replace by parameter return records.with_context(commit_gamification=commit)._update_all() def _update_all(self): """Update the challenges and related goals :param list(int) ids: the ids of the challenges to update, if False will update only challenges in progress.""" if not self: return True Goals = self.env['gamification.goal'] # include yesterday goals to update the goals that just ended # exclude goals for users that did not connect since the last update yesterday = fields.Date.to_string(date.today() - timedelta(days=1)) self.env.cr.execute("""SELECT gg.id FROM gamification_goal as gg, gamification_challenge as gc, res_users as ru, res_users_log as log WHERE gg.challenge_id = gc.id AND gg.user_id = ru.id AND ru.id = log.create_uid AND gg.write_date < log.create_date AND gg.closed IS false AND gc.id IN %s AND (gg.state = 'inprogress' OR (gg.state = 'reached' AND (gg.end_date >= %s OR gg.end_date IS NULL))) GROUP BY gg.id """, [tuple(self.ids), yesterday]) Goals.browse(goal_id for [goal_id] in self.env.cr.fetchall()).update_goal() self._recompute_challenge_users() self._generate_goals_from_challenge() for challenge in self: if challenge.last_report_date != fields.Date.today(): # goals closed but still opened at the last report date closed_goals_to_report = Goals.search([ ('challenge_id', '=', challenge.id), ('start_date', '>=', challenge.last_report_date), ('end_date', '<=', challenge.last_report_date) ]) if challenge.next_report_date and fields.Date.today() >= challenge.next_report_date: challenge.report_progress() elif closed_goals_to_report: # some goals need a final report challenge.report_progress(subset_goals=closed_goals_to_report) self._check_challenge_reward() return True def _get_challenger_users(self, domain): # FIXME: literal_eval? user_domain = safe_eval(domain) return self.env['res.users'].search(user_domain) def _recompute_challenge_users(self): """Recompute the domain to add new users and remove the one no longer matching the domain""" for challenge in self.filtered(lambda c: c.user_domain): current_users = challenge.user_ids new_users = self._get_challenger_users(challenge.user_domain) if current_users != new_users: challenge.user_ids = new_users return True @api.multi def action_start(self): """Start a challenge""" return self.write({'state': 'inprogress'}) @api.multi def action_check(self): """Check a challenge Create goals that haven't been created yet (eg: if added users) Recompute the current value for each goal related""" self.env['gamification.goal'].search([ ('challenge_id', 'in', self.ids), ('state', '=', 'inprogress') ]).unlink() return self._update_all() @api.multi def action_report_progress(self): """Manual report of a goal, does not influence automatic report frequency""" for challenge in self: challenge.report_progress() return True ##### Automatic actions ##### def _generate_goals_from_challenge(self): """Generate the goals for each line and user. If goals already exist for this line and user, the line is skipped. This can be called after each change in the list of users or lines. :param list(int) ids: the list of challenge concerned""" Goals = self.env['gamification.goal'] for challenge in self: (start_date, end_date) = start_end_date_for_period(challenge.period, challenge.start_date, challenge.end_date) to_update = Goals.browse(()) for line in challenge.line_ids: # there is potentially a lot of users # detect the ones with no goal linked to this line date_clause = "" query_params = [line.id] if start_date: date_clause += "AND g.start_date = %s" query_params.append(start_date) if end_date: date_clause += "AND g.end_date = %s" query_params.append(end_date) query = """SELECT u.id AS user_id FROM res_users u LEFT JOIN gamification_goal g ON (u.id = g.user_id) WHERE line_id = %s {date_clause} """.format(date_clause=date_clause) self.env.cr.execute(query, query_params) user_with_goal_ids = {it for [it] in self.env.cr._obj} participant_user_ids = set(challenge.user_ids.ids) user_squating_challenge_ids = user_with_goal_ids - participant_user_ids if user_squating_challenge_ids: # users that used to match the challenge Goals.search([ ('challenge_id', '=', challenge.id), ('user_id', 'in', list(user_squating_challenge_ids)) ]).unlink() values = { 'definition_id': line.definition_id.id, 'line_id': line.id, 'target_goal': line.target_goal, 'state': 'inprogress', } if start_date: values['start_date'] = start_date if end_date: values['end_date'] = end_date # the goal is initialised over the limit to make sure we will compute it at least once if line.condition == 'higher': values['current'] = line.target_goal - 1 else: values['current'] = line.target_goal + 1 if challenge.remind_update_delay: values['remind_update_delay'] = challenge.remind_update_delay for user_id in (participant_user_ids - user_with_goal_ids): values['user_id'] = user_id to_update |= Goals.create(values) to_update.update_goal() return True ##### JS utilities ##### def _get_serialized_challenge_lines(self, user=(), restrict_goals=(), restrict_top=0): """Return a serialised version of the goals information if the user has not completed every goal :param user: user retrieving progress (False if no distinction, only for ranking challenges) :param restrict_goals: compute only the results for this subset of gamification.goal ids, if False retrieve every goal of current running challenge :param int restrict_top: for challenge lines where visibility_mode is ``ranking``, retrieve only the best ``restrict_top`` results and itself, if 0 retrieve all restrict_goal_ids has priority over restrict_top format list # if visibility_mode == 'ranking' { 'name': <gamification.goal.description name>, 'description': <gamification.goal.description description>, 'condition': <reach condition {lower,higher}>, 'computation_mode': <target computation {manually,count,sum,python}>, 'monetary': <{True,False}>, 'suffix': <value suffix>, 'action': <{True,False}>, 'display_mode': <{progress,boolean}>, 'target': <challenge line target>, 'own_goal_id': <gamification.goal id where user_id == uid>, 'goals': [ { 'id': <gamification.goal id>, 'rank': <user ranking>, 'user_id': <res.users id>, 'name': <res.users name>, 'state': <gamification.goal state {draft,inprogress,reached,failed,canceled}>, 'completeness': <percentage>, 'current': <current value>, } ] }, # if visibility_mode == 'personal' { 'id': <gamification.goal id>, 'name': <gamification.goal.description name>, 'description': <gamification.goal.description description>, 'condition': <reach condition {lower,higher}>, 'computation_mode': <target computation {manually,count,sum,python}>, 'monetary': <{True,False}>, 'suffix': <value suffix>, 'action': <{True,False}>, 'display_mode': <{progress,boolean}>, 'target': <challenge line target>, 'state': <gamification.goal state {draft,inprogress,reached,failed,canceled}>, 'completeness': <percentage>, 'current': <current value>, } """ Goals = self.env['gamification.goal'] (start_date, end_date) = start_end_date_for_period(self.period) res_lines = [] for line in self.line_ids: line_data = { 'name': line.definition_id.name, 'description': line.definition_id.description, 'condition': line.definition_id.condition, 'computation_mode': line.definition_id.computation_mode, 'monetary': line.definition_id.monetary, 'suffix': line.definition_id.suffix, 'action': True if line.definition_id.action_id else False, 'display_mode': line.definition_id.display_mode, 'target': line.target_goal, } domain = [ ('line_id', '=', line.id), ('state', '!=', 'draft'), ] if restrict_goals: domain.append(('ids', 'in', restrict_goals.ids)) else: # if no subset goals, use the dates for restriction if start_date: domain.append(('start_date', '=', start_date)) if end_date: domain.append(('end_date', '=', end_date)) if self.visibility_mode == 'personal': if not user: raise exceptions.UserError(_("Retrieving progress for personal challenge without user information")) domain.append(('user_id', '=', user.id)) goal = Goals.search(domain, limit=1) if not goal: continue if goal.state != 'reached': return [] line_data.update(goal.read(['id', 'current', 'completeness', 'state'])[0]) res_lines.append(line_data) continue line_data['own_goal_id'] = False, line_data['goals'] = [] if line.condition=='higher': goals = Goals.search(domain, order="completeness desc, current desc") else: goals = Goals.search(domain, order="completeness desc, current asc") if not goals: continue for ranking, goal in enumerate(goals): if user and goal.user_id == user: line_data['own_goal_id'] = goal.id elif restrict_top and ranking > restrict_top: # not own goal and too low to be in top continue line_data['goals'].append({ 'id': goal.id, 'user_id': goal.user_id.id, 'name': goal.user_id.name, 'rank': ranking, 'current': goal.current, 'completeness': goal.completeness, 'state': goal.state, }) if len(goals) < 3: # display at least the top 3 in the results missing = 3 - len(goals) for ranking, mock_goal in enumerate([{'id': False, 'user_id': False, 'name': '', 'current': 0, 'completeness': 0, 'state': False}] * missing, start=len(goals)): mock_goal['rank'] = ranking line_data['goals'].append(mock_goal) res_lines.append(line_data) return res_lines ##### Reporting ##### def report_progress(self, users=(), subset_goals=False): """Post report about the progress of the goals :param users: users that are concerned by the report. If False, will send the report to every user concerned (goal users and group that receive a copy). Only used for challenge with a visibility mode set to 'personal'. :param subset_goals: goals to restrict the report """ challenge = self MailTemplates = self.env['mail.template'] if challenge.visibility_mode == 'ranking': lines_boards = challenge._get_serialized_challenge_lines(restrict_goals=subset_goals) body_html = MailTemplates.with_context(challenge_lines=lines_boards)._render_template(challenge.report_template_id.body_html, 'gamification.challenge', challenge.id) # send to every follower and participant of the challenge challenge.message_post( body=body_html, partner_ids=challenge.mapped('user_ids.partner_id.id'), subtype='mail.mt_comment', notif_layout='mail.mail_notification_light', ) if challenge.report_message_group_id: challenge.report_message_group_id.message_post( body=body_html, subtype='mail.mt_comment') else: # generate individual reports for user in (users or challenge.user_ids): lines = challenge._get_serialized_challenge_lines(user, restrict_goals=subset_goals) if not lines: continue body_html = MailTemplates.sudo(user).with_context(challenge_lines=lines)._render_template( challenge.report_template_id.body_html, 'gamification.challenge', challenge.id) # send message only to users, not on the challenge self.env['gamification.challenge'].message_post( body=body_html, partner_ids=[(4, user.partner_id.id)], subtype='mail.mt_comment', notif_layout='mail.mail_notification_light', ) if challenge.report_message_group_id: challenge.report_message_group_id.message_post( body=body_html, subtype='mail.mt_comment', notif_layout='mail.mail_notification_light', ) return challenge.write({'last_report_date': fields.Date.today()}) ##### Challenges ##### @api.multi def accept_challenge(self): user = self.env.user sudoed = self.sudo() sudoed.message_post(body=_("%s has joined the challenge") % user.name) sudoed.write({'invited_user_ids': [(3, user.id)], 'user_ids': [(4, user.id)]}) return sudoed._generate_goals_from_challenge() @api.multi def discard_challenge(self): """The user discard the suggested challenge""" user = self.env.user sudoed = self.sudo() sudoed.message_post(body=_("%s has refused the challenge") % user.name) return sudoed.write({'invited_user_ids': (3, user.id)}) def _check_challenge_reward(self, force=False): """Actions for the end of a challenge If a reward was selected, grant it to the correct users. Rewards granted at: - the end date for a challenge with no periodicity - the end of a period for challenge with periodicity - when a challenge is manually closed (if no end date, a running challenge is never rewarded) """ commit = self.env.context.get('commit_gamification') and self.env.cr.commit for challenge in self: (start_date, end_date) = start_end_date_for_period(challenge.period, challenge.start_date, challenge.end_date) yesterday = date.today() - timedelta(days=1) rewarded_users = self.env['res.users'] challenge_ended = force or end_date == fields.Date.to_string(yesterday) if challenge.reward_id and (challenge_ended or challenge.reward_realtime): # not using start_date as intemportal goals have a start date but no end_date reached_goals = self.env['gamification.goal'].read_group([ ('challenge_id', '=', challenge.id), ('end_date', '=', end_date), ('state', '=', 'reached') ], fields=['user_id'], groupby=['user_id']) for reach_goals_user in reached_goals: if reach_goals_user['user_id_count'] == len(challenge.line_ids): # the user has succeeded every assigned goal user = self.env['res.users'].browse(reach_goals_user['user_id'][0]) if challenge.reward_realtime: badges = self.env['gamification.badge.user'].search_count([ ('challenge_id', '=', challenge.id), ('badge_id', '=', challenge.reward_id.id), ('user_id', '=', user.id), ]) if badges > 0: # has already recieved the badge for this challenge continue challenge._reward_user(user, challenge.reward_id) rewarded_users |= user if commit: commit() if challenge_ended: # open chatter message message_body = _("The challenge %s is finished.") % challenge.name if rewarded_users: user_names = rewarded_users.name_get() message_body += _("<br/>Reward (badge %s) for every succeeding user was sent to %s.") % (challenge.reward_id.name, ", ".join(name for (user_id, name) in user_names)) else: message_body += _("<br/>Nobody has succeeded to reach every goal, no badge is rewarded for this challenge.") # reward bests reward_message = _("<br/> %(rank)d. %(user_name)s - %(reward_name)s") if challenge.reward_first_id: (first_user, second_user, third_user) = challenge._get_topN_users(MAX_VISIBILITY_RANKING) if first_user: challenge._reward_user(first_user, challenge.reward_first_id) message_body += _("<br/>Special rewards were sent to the top competing users. The ranking for this challenge is :") message_body += reward_message % { 'rank': 1, 'user_name': first_user.name, 'reward_name': challenge.reward_first_id.name, } else: message_body += _("Nobody reached the required conditions to receive special badges.") if second_user and challenge.reward_second_id: challenge._reward_user(second_user, challenge.reward_second_id) message_body += reward_message % { 'rank': 2, 'user_name': second_user.name, 'reward_name': challenge.reward_second_id.name, } if third_user and challenge.reward_third_id: challenge._reward_user(third_user, challenge.reward_third_id) message_body += reward_message % { 'rank': 3, 'user_name': third_user.name, 'reward_name': challenge.reward_third_id.name, } challenge.message_post( partner_ids=[user.partner_id.id for user in challenge.user_ids], body=message_body) if commit: commit() return True def _get_topN_users(self, n): """Get the top N users for a defined challenge Ranking criterias: 1. succeed every goal of the challenge 2. total completeness of each goal (can be over 100) Only users having reached every goal of the challenge will be returned unless the challenge ``reward_failure`` is set, in which case any user may be considered. :returns: an iterable of exactly N records, either User objects or False if there was no user for the rank. There can be no False between two users (if users[k] = False then users[k+1] = False """ Goals = self.env['gamification.goal'] (start_date, end_date) = start_end_date_for_period(self.period, self.start_date, self.end_date) challengers = [] for user in self.user_ids: all_reached = True total_completeness = 0 # every goal of the user for the running period goal_ids = Goals.search([ ('challenge_id', '=', self.id), ('user_id', '=', user.id), ('start_date', '=', start_date), ('end_date', '=', end_date) ]) for goal in goal_ids: if goal.state != 'reached': all_reached = False if goal.definition_condition == 'higher': # can be over 100 total_completeness += 100.0 * goal.current / goal.target_goal elif goal.state == 'reached': # for lower goals, can not get percentage so 0 or 100 total_completeness += 100 challengers.append({'user': user, 'all_reached': all_reached, 'total_completeness': total_completeness}) challengers.sort(key=lambda k: (k['all_reached'], k['total_completeness']), reverse=True) if not self.reward_failure: # only keep the fully successful challengers at the front, could # probably use filter since the successful ones are at the front challengers = itertools.takewhile(lambda c: c['all_reached'], challengers) # append a tail of False, then keep the first N challengers = itertools.islice( itertools.chain( (c['user'] for c in challengers), itertools.repeat(False), ), 0, n ) return tuple(challengers) def _reward_user(self, user, badge): """Create a badge user and send the badge to him :param user: the user to reward :param badge: the concerned badge """ return self.env['gamification.badge.user'].create({ 'user_id': user.id, 'badge_id': badge.id, 'challenge_id': self.id })._send_badge()
class WorkshopVehicleLogServices(models.Model): _name = 'workshop.vehicle.log.services' _inherits = {'workshop.vehicle.cost': 'cost_id'} _description = 'Services for vehicles' @api.model def default_get(self, default_fields): res = super(WorkshopVehicleLogServices, self).default_get(default_fields) service = self.env.ref('workshop.type_service_service_8', raise_if_not_found=False) res.update({ 'date': fields.Date.context_today(self), 'date_in': fields.Date.context_today(self), 'cost_subtype_id': service and service.id or False, 'cost_type': 'services' }) return res name = fields.Char(string='Job Card #', required=True, copy=False, readonly=True, index=True, default=lambda self: _('New')) task_id = fields.Many2one('project.task', 'Work Order') date_in = fields.Date( help='The date the vehicle was brought into the workshop', store=True) date_out = fields.Date(help='The date the vehicle left the workshop', store=True) project_id = fields.Many2one('project.project', string='Nature Of Job', required=True, domain=[ ('name', 'in', ['In Workshop Service', 'Field Service']) ]) state = fields.Selection([ ('pending', 'Pending'), ('awaiting_confirmation', 'Awaiting Confirmation'), ('awaiting_parts', 'Awaiting Parts'), ('in_progress', 'Repair In Progress'), ('awaiting_payment', 'Awaiting Payment'), ('done', 'Complete'), ], string='Status', readonly=True, copy=False, index=True, tracking=3, default='pending') fuel_log = fields.Selection([('empty', 'Empty'), ('half', 'Half Full'), ('full', 'Full')], string='Fuel') # vendor_id = fields.Many2one('res.partner', 'Vendor') # we need to keep this field as a related with store=True because the graph view doesn't support # (1) to address fields from inherited table and (2) fields that aren't stored in database cost_amount = fields.Float(related='task_id.material_line_total_price', string='Amount', store=True, readonly=True) notes = fields.Text() cost_id = fields.Many2one('workshop.vehicle.cost', 'Cost', required=True, ondelete='cascade') def action_in_progress(self): return self.write({'state': 'in_progress'}) def action_done(self): return self.write({'state': 'done'}) def action_cancel(self): return self.write({'state': 'cancel'}) @api.onchange('task_id.material_line_total_price') def _onchange_cost_amount(self): self.amount = self.cost_amount @api.onchange('vehicle_id') def _onchange_vehicle(self): if self.vehicle_id: self.odometer_unit = self.vehicle_id.odometer_unit self.owner_id = self.vehicle_id.driver_id.id @api.model def create(self, vals): seq_date = None if vals.get('name', _('New')) == _('New'): vals['name'] = self.env['ir.sequence'].next_by_code( 'workshop.job.card.sequence', sequence_date=seq_date) or _('New') result = super(WorkshopVehicleLogServices, self).create(vals) return result def action_view_task(self): self.ensure_one() return { "type": "ir.actions.act_window", "res_model": "project.task", "views": [[False, "form"]], "res_id": self.task_id.id, "context": { "create": False, "show_sale": True }, }
class WeatherForecastDate(models.Model): _name = "weather.forecast.date" _description = "Weather Forecast Date" _rec_name = "date" _order = "id asc" # This function is currently cannot work due to timeout # def update_weather_data(self): # try: # file = requests.get('https://weather.com/en-GB/weather/tenday/l/21.00,105.84') # soup = BeautifulSoup(file.text, 'html.parser') # list = [] # content = soup.find_all("div", {"data-testid": "DetailsSummary"}) # for items in content: # dict = {} # day = items.find_all("span") # try: # dict["high_temp"] = int(day[0].text.split('°')[0]) # dict["low_temp"] = int(day[2].text.split('°')[0]) # dict["weather"] = day[3].text.split('/')[0].replace('AM ', '') # dict["rain_chance"] = int(day[4].text.split('%')[0]) # dict["wind_speed"] = round(int(day[5].text.split(' ')[1]) * 1.609344, 1) # except: # dict["high_temp"] = "" # dict["low_temp"] = "" # dict["weather"] = "None" # dict["rain_chance"] = "" # dict["wind_speed"] = "" # list.append(dict) # date = datetime.date.today() # for i in range(9): # list[i]['date'] = date # date_info = request.env['weather.forecast.date'].sudo().search([('date', '=', date)], limit=1) # if date_info: # date_info.sudo().write(list[i]) # print(date_info) # else: # new_date_info = request.env['weather.forecast.date'].sudo().create({'date': date}) # new_date_info.sudo().write(list[i]) # print(new_date_info) # date += datetime.timedelta(days=1) # print("Data updated") # except: # print("Error") @api.constrains('date') def _check_date(self): date = self.env['weather.forecast.date'].search([('date', '=', self.date), ('id', '!=', self.id)]) if date: raise ValidationError(_('Record of this day already existed.')) @api.depends('weather') def _choose_icon(self): for rec in self: if 'Cloud' in rec.weather: rec.weather_icon = 'cloud' elif 'Storm' in rec.weather or 'storm' in rec.weather: rec.weather_icon = 'storm' elif 'Rain' in rec.weather or 'Shower' in rec.weather: rec.weather_icon = 'rain' else: rec.weather_icon = 'sun' @api.depends('date') def _get_day(self): for rec in self: if rec.date.weekday() == 0: rec.day = 'Monday' elif rec.date.weekday() == 1: rec.day = 'Tuesday' elif rec.date.weekday() == 2: rec.day = 'Wednesday' elif rec.date.weekday() == 3: rec.day = 'Thursday' elif rec.date.weekday() == 4: rec.day = 'Friday' elif rec.date.weekday() == 5: rec.day = 'Saturday' else: rec.day = 'Sunday' date = fields.Date(string='Date', required=True, copy=False) day = fields.Selection([ ('Monday', 'Monday'), ('Tuesday', 'Tuesday'), ('Wednesday', 'Wednesday'), ('Thursday', 'Thursday'), ('Friday', 'Friday'), ('Saturday', 'Saturday'), ('Sunday', 'Sunday'), ], default='mon', compute='_get_day', readonly=True) weather_icon = fields.Selection([ ('rain', 'Rainy'), ('sun', 'Sunny'), ('cloud', 'Cloudy'), ('storm', 'Storm'), ], default='sun', string="Weather Icon", compute='_choose_icon') weather = fields.Char(string='Weather') rain_chance = fields.Integer(string='Chance of Rain (%)') high_temp = fields.Integer(string='Highest Temperature(°C)') low_temp = fields.Integer(string='Lowest Temperature(°C)') wind_speed = fields.Float(string='Wind Speed (kmh)')
class EducationStudentsAttendance(models.Model): _name = 'education.attendance' name = fields.Char(string='Name', default='New') class_id = fields.Many2one('education.class', string='Class') division_id = fields.Many2one('education.class.division', string='Division', required=True) date = fields.Date(string='Date', default=fields.Date.today, required=True) attendance_line = fields.One2many('education.attendance.line', 'attendance_id', string='Attendance Line') attendance_created = fields.Boolean(string='Attendance Created') all_marked_morning = fields.Boolean(string='All students are present in the morning') all_marked_afternoon = fields.Boolean(string='All students are present in the afternoon') state = fields.Selection([('draft', 'Draft'), ('done', 'Done')], default='draft') company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env['res.company']._company_default_get()) academic_year = fields.Many2one('education.academic.year', string='Academic Year', related='division_id.academic_year_id', store=True) faculty_id = fields.Many2one('education.faculty', string='Faculty') @api.model def create(self, vals): res = super(EducationStudentsAttendance, self).create(vals) res.class_id = res.division_id.class_id.id attendance_obj = self.env['education.attendance'] already_created_attendance = attendance_obj.search( [('division_id', '=', res.division_id.id), ('date', '=', res.date), ('company_id', '=', res.company_id.id)]) if len(already_created_attendance) > 1: raise ValidationError( _('Attendance register of %s is already created on "%s"', ) % (res.division_id.name, res.date)) return res @api.multi def create_attendance_line(self): self.name = str(self.date) attendance_line_obj = self.env['education.attendance.line'] students = self.division_id.student_ids if len(students) < 1: raise UserError(_('There are no students in this Division')) for student in students: data = { 'name': self.name, 'attendance_id': self.id, 'student_id': student.id, 'student_name': student.name, 'class_id': self.division_id.class_id.id, 'division_id': self.division_id.id, 'date': self.date, } attendance_line_obj.create(data) self.attendance_created = True @api.multi def mark_all_present_morning(self): for records in self.attendance_line: records.present_morning = True self.all_marked_morning = True @api.multi def un_mark_all_present_morning(self): for records in self.attendance_line: records.present_morning = False self.all_marked_morning = False @api.multi def mark_all_present_afternoon(self): for records in self.attendance_line: records.present_afternoon = True self.all_marked_afternoon = True @api.multi def un_mark_all_present_afternoon(self): for records in self.attendance_line: records.present_afternoon = False self.all_marked_afternoon = False @api.multi def attendance_done(self): for records in self.attendance_line: records.state = 'done' if not records.present_morning and not records.present_afternoon: records.full_day_absent = 1 elif not records.present_morning: records.half_day_absent = 1 elif not records.present_afternoon: records.half_day_absent = 1 self.state = 'done' @api.multi def set_to_draft(self): for records in self.attendance_line: records.state = 'draft' self.state = 'draft'
class AccountMoveLine(models.Model): _inherit = 'account.move.line' invoice_tax_line_id = fields.Many2one( comodel_name='account.invoice.tax', copy=True, ondelete='restrict', ) payment_tax_line_id = fields.Many2one( comodel_name='account.payment.tax', copy=True, ondelete='restrict', ) tax_invoice_manual = fields.Char( string='Tax Invoice Number', copy=True, ) tax_invoice = fields.Char( compute='_compute_tax_invoice', store=True, ) tax_date_manual = fields.Date( copy=True, ) tax_date = fields.Char( compute='_compute_tax_invoice', store=True, ) @api.model def create(self, vals): """ Create payment tax line for clear undue vat """ if self._context.get('cash_basis_entry_move_line', False): move_line = self._context['cash_basis_entry_move_line'] payment = self._context.get('payment') invoice_tax_line = move_line.invoice_tax_line_id payment_tax_line_id = False if move_line.tax_line_id.tax_exigibility == 'on_payment' and \ move_line.tax_line_id.type_tax_use == 'purchase': payment_tax = self.env['account.payment.tax'].\ search([('invoice_tax_line_id', '=', invoice_tax_line.id), ('payment_id', '=', payment.id)]) if not payment_tax: # If not already created for this payment currency = self.env.user.company_id.currency_id payment_tax = self.env['account.payment.tax'].create({ 'partner_id': payment.partner_id.id, 'invoice_tax_line_id': invoice_tax_line.id, 'name': invoice_tax_line.name, 'company_currency_id': currency.id, 'payment_id': payment.id, }) payment_tax_line_id = payment_tax.id vals.update({ 'invoice_tax_line_id': invoice_tax_line.id, 'tax_invoice_manual': invoice_tax_line.tax_invoice_manual, 'payment_tax_line_id': payment_tax_line_id, }) res = super().create(vals) return res @api.multi @api.depends('tax_invoice_manual', 'tax_date_manual', 'invoice_tax_line_id', 'invoice_id.number', 'invoice_id.date_invoice') def _compute_tax_invoice(self): for ml in self.filtered('tax_line_id'): # For undue tax on supplier invoice if ml.tax_line_id.tax_exigibility == 'on_payment' and \ ml.tax_line_id.type_tax_use == 'purchase' and \ ml.invoice_id: ml.tax_invoice = False ml.tax_date = False else: # other cases ml.tax_invoice = ml.tax_invoice_manual or \ ml.invoice_tax_line_id.tax_invoice or \ ml.invoice_id.number ml.tax_date = ml.tax_date_manual or \ ml.invoice_tax_line_id.tax_date or \ ml.invoice_id.date_invoice return True
class IrConfigParameter(models.Model): """Per-database storage of configuration key-value pairs.""" _name = 'ir.config_parameter' _description = 'System Parameter' _rec_name = 'key' _order = 'key' key = fields.Char(required=True, index=True) value = fields.Text(required=True) _sql_constraints = [('key_uniq', 'unique (key)', 'Key must be unique.')] @mute_logger('odoo.addons.base.models.ir_config_parameter') def init(self, force=False): """ Initializes the parameters listed in _default_parameters. It overrides existing parameters if force is ``True``. """ for key, func in _default_parameters.items(): # force=True skips search and always performs the 'if' body (because ids=False) params = self.sudo().search([('key', '=', key)]) if force or not params: params.set_param(key, func()) @api.model def get_param(self, key, default=False): """Retrieve the value for a given key. :param string key: The key of the parameter value to retrieve. :param string default: default value if parameter is missing. :return: The value of the parameter, or ``default`` if it does not exist. :rtype: string """ return self._get_param(key) or default @api.model @ormcache('self.env.uid', 'self.env.su', 'key') def _get_param(self, key): params = self.search_read([('key', '=', key)], fields=['value'], limit=1) return params[0]['value'] if params else None @api.model def set_param(self, key, value): """Sets the value of a parameter. :param string key: The key of the parameter value to set. :param string value: The value to set. :return: the previous value of the parameter or False if it did not exist. :rtype: string """ param = self.search([('key', '=', key)]) if param: old = param.value if value is not False and value is not None: if str(value) != old: param.write({'value': value}) else: param.unlink() return old else: if value is not False and value is not None: self.create({'key': key, 'value': value}) return False @api.model_create_multi def create(self, vals_list): self.clear_caches() return super(IrConfigParameter, self).create(vals_list) @api.multi def write(self, vals): self.clear_caches() return super(IrConfigParameter, self).write(vals) @api.multi def unlink(self): self.clear_caches() return super(IrConfigParameter, self).unlink()
class OrderType(models.Model): '''new model order.type''' _name = 'order.type' _description = 'Order Type' name = fields.Char('Order Type')
class tup(models.Model): _name = 'anggaran.tup' name = fields.Char('Nomor', readonly=True, required=True) tanggal = fields.Date('Tanggal', required=True) tahun_id = fields.Many2one('anggaran.fiscalyear', 'Tahun') lampiran = fields.Integer('Lampiran') perihal = fields.Char('Perihal', required=True) kepada = fields.Text('Kepada', required=True) dasar_rkat = fields.Char('Dasar RKAT Nomor/Tanggal', required=True) jumlah = fields.Float('Jumlah', required=True) unit_id = fields.Many2one('anggaran.unit', 'Atas Nama', required=True) nomor_rek = fields.Char('Nomor Rekening') nama_bank = fields.Char('Nama Bank') pumkc_id = fields.Many2one('hr.employee', 'PUMKC') nip_pumkc = fields.Related('pumkc_id', 'otherid', type='char', relation='hr.employee', string='NIP PUMKC', store=True, readonly=True) atasan_pumkc_id = fields.Many2one('hr.employee', 'Atasan Langsung PUMKC') nip_atasan_pumkc = fields.Related('atasan_pumkc_id', 'otherid', type='char', relation='hr.employee', string='NIP Atasan PUMKC', store=True, readonly=True) state = fields.Selection(TUP_STATES, 'Status', readonly=True, required=True) user_id = fields.Many2one('res.users', 'Created') _defaults = { 'state': TUP_STATES[0][0], 'tanggal': lambda *a: time.strftime("%Y-%m-%d"), 'user_id': lambda obj, cr, uid, context: uid, 'name': lambda obj, cr, uid, context: '/', } def create(self, vals): if vals.get('name', '/') == '/': vals['name'] = self.env['ir.sequence'].get('anggaran.tup') or '/' new_id = super(tup, self).create(vals) return new_id def action_draft(self, cr, uid, ids, context=None): #set to "draft" state return self.write(cr, uid, ids, {'state': TUP_STATES[0][0]}, context=context) def action_confirm(self, cr, uid, ids, context=None): #set to "confirmed" state return self.write(cr, uid, ids, {'state': TUP_STATES[1][0]}, context=context) def action_reject(self, cr, uid, ids, context=None): #set to "reject" state return self.write(cr, uid, ids, {'state': TUP_STATES[2][0]}, context=context) def action_done(self, cr, uid, ids, context=None): #set to "done" state return self.write(cr, uid, ids, {'state': TUP_STATES[3][0]}, context=context) def action_create_spm(self, cr, uid, ids, context=None): tup = self.browse(ids[0], context) spm_obj = self.env["anggaran.spm"] data = { 'name': '/', 'tanggal': time.strftime("%Y-%m-%d"), 'cara_bayar': 'up', 'unit_id': tup.unit_id.id, 'tahun_id': tup.tahun_id.id, 'jumlah': tup.jumlah, 'sisa': 0.0, 'user_id': uid, 'state': 'draft', 'tup_id': tup.id } spm_id = spm_obj.create(cr, uid, data, context) return spm_id
class AccountMove(models.Model): _inherit = 'account.move' l10n_it_send_state = fields.Selection([ ('new', 'New'), ('other', 'Other'), ('to_send', 'Not yet send'), ('sent', 'Sent, waiting for response'), ('invalid', 'Sent, but invalid'), ('delivered', 'This invoice is delivered'), ('delivered_accepted', 'This invoice is delivered and accepted by destinatory'), ('delivered_refused', 'This invoice is delivered and refused by destinatory'), ('delivered_expired', 'This invoice is delivered and expired (expiry of the maximum term for communication of acceptance/refusal)'), ('failed_delivery', 'Delivery impossible, ES certify that it has received the invoice and that the file \ could not be delivered to the addressee') # ok we must do nothing ], default='to_send', copy=False) l10n_it_stamp_duty = fields.Float(default=0, string="Dati Bollo", readonly=True, states={'draft': [('readonly', False)]}) l10n_it_ddt_id = fields.Many2one('l10n_it.ddt', string='DDT', readonly=True, states={'draft': [('readonly', False)]}, copy=False) l10n_it_einvoice_name = fields.Char(readonly=True, copy=False) l10n_it_einvoice_id = fields.Many2one('ir.attachment', string="Electronic invoice", copy=False) def post(self): # OVERRIDE super(AccountMove, self).post() # Retrieve invoices to generate the xml. invoices_to_export = self.filtered(lambda move: move.company_id.country_id == self.env.ref('base.it') and move.is_sale_document() and move.l10n_it_send_state not in ['sent', 'delivered', 'delivered_accepted']) invoices_to_export.write({'l10n_it_send_state': 'other'}) invoices_to_send = self.env['account.move'] invoices_other = self.env['account.move'] for invoice in invoices_to_export: invoice._check_before_xml_exporting() invoice.invoice_generate_xml() if len(invoice.commercial_partner_id.l10n_it_pa_index or '') == 6: invoice.message_post( body=(_("Invoices for PA are not managed by Odoo, you can download the document and send it on your own.")) ) invoices_other += invoice else: invoices_to_send += invoice invoices_other.write({'l10n_it_send_state': 'other'}) invoices_to_send.write({'l10n_it_send_state': 'to_send'}) for invoice in invoices_to_send: invoice.send_pec_mail() def _check_before_xml_exporting(self): seller = self.company_id buyer = self.commercial_partner_id # <1.1.1.1> if not seller.country_id: raise UserError(_("%s must have a country") % (seller.display_name)) # <1.1.1.2> if not seller.vat: raise UserError(_("%s must have a VAT number") % (seller.display_name)) elif len(seller.vat) > 30: raise UserError(_("The maximum length for VAT number is 30. %s have a VAT number too long: %s.") % (seller.display_name, seller.vat)) # <1.2.1.2> if not seller.l10n_it_codice_fiscale: raise UserError(_("%s must have a codice fiscale number") % (seller.display_name)) # <1.2.1.8> if not seller.l10n_it_tax_system: raise UserError(_("The seller's company must have a tax system.")) # <1.2.2> if not seller.street and not seller.street2: raise UserError(_("%s must have a street.") % (seller.display_name)) if not seller.zip: raise UserError(_("%s must have a post code.") % (seller.display_name)) if len(seller.zip) != 5 and seller.country_id.code == 'IT': raise UserError(_("%s must have a post code of length 5.") % (seller.display_name)) if not seller.city: raise UserError(_("%s must have a city.") % (seller.display_name)) if not seller.country_id: raise UserError(_("%s must have a country.") % (seller.display_name)) if seller.l10n_it_has_tax_representative and not seller.l10n_it_tax_representative_partner_id.vat: raise UserError(_("Tax representative partner %s of %s must have a tax number.") % (seller.l10n_it_tax_representative_partner_id.display_name, seller.display_name)) # <1.4.1> if not buyer.vat and not buyer.l10n_it_codice_fiscale and buyer.country_id.code == 'IT': raise UserError(_("The buyer, %s, or his company must have either a VAT number either a tax code (Codice Fiscale).") % (buyer.display_name)) # <1.4.2> if not buyer.street and not buyer.street2: raise UserError(_("%s must have a street.") % (buyer.display_name)) if not buyer.zip: raise UserError(_("%s must have a post code.") % (buyer.display_name)) if len(buyer.zip) != 5 and buyer.country_id.code == 'IT': raise UserError(_("%s must have a post code of length 5.") % (buyer.display_name)) if not buyer.city: raise UserError(_("%s must have a city.") % (buyer.display_name)) if not buyer.country_id: raise UserError(_("%s must have a country.") % (buyer.display_name)) # <2.2.1> for invoice_line in self.invoice_line_ids: if len(invoice_line.tax_ids) != 1: raise UserError(_("You must select one and only one tax by line.")) for tax_line in self.line_ids.filtered(lambda line: line.tax_line_id): if not tax_line.tax_line_id.l10n_it_has_exoneration and tax_line.tax_line_id.amount == 0: raise ValidationError(_("%s has an amount of 0.0, you must indicate the kind of exoneration." % tax_line.name)) def invoice_generate_xml(self): for invoice in self: if invoice.l10n_it_einvoice_id and invoice.l10n_it_send_state not in ['invalid', 'to_send']: raise UserError(_("You can't regenerate an E-Invoice when the first one is sent and there are no errors")) if invoice.l10n_it_einvoice_id: invoice.l10n_it_einvoice_id.unlink() a = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" n = invoice.id progressive_number = "" while n: (n,m) = divmod(n,len(a)) progressive_number = a[m] + progressive_number report_name = '%(country_code)s%(codice)s_%(progressive_number)s.xml' % { 'country_code': invoice.company_id.country_id.code, 'codice': invoice.company_id.l10n_it_codice_fiscale, 'progressive_number': progressive_number.zfill(5), } invoice.l10n_it_einvoice_name = report_name data = b"<?xml version='1.0' encoding='UTF-8'?>" + invoice._export_as_xml() description = _('Italian invoice: %s') % invoice.move_type invoice.l10n_it_einvoice_id = self.env['ir.attachment'].create({ 'name': report_name, 'res_id': invoice.id, 'res_model': invoice._name, 'datas': base64.encodebytes(data), 'description': description, 'type': 'binary', }) invoice.message_post( body=(_("E-Invoice is generated on %s by %s") % (fields.Datetime.now(), self.env.user.display_name)) ) def _export_as_xml(self): ''' Create the xml file content. :return: The XML content as str. ''' self.ensure_one() def format_date(dt): # Format the date in the italian standard. dt = dt or datetime.now() return dt.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT) def format_monetary(number, currency): # Format the monetary values to avoid trailing decimals (e.g. 90.85000000000001). return float_repr(number, min(2, currency.decimal_places)) def format_numbers(number): #format number to str with between 2 and 8 decimals (event if it's .00) number_splited = str(number).split('.') if len(number_splited) == 1: return "%.02f" % number cents = number_splited[1] if len(cents) > 8: return "%.08f" % number return float_repr(number, max(2, len(cents))) def format_numbers_two(number): #format number to str with 2 (event if it's .00) return "%.02f" % number def discount_type(discount): return 'SC' if discount > 0 else 'MG' def format_phone(number): if not number: return False number = number.replace(' ', '').replace('/', '').replace('.', '') if len(number) > 4 and len(number) < 13: return number return False def get_vat_number(vat): return vat[2:].replace(' ', '') def get_vat_country(vat): return vat[:2].upper() formato_trasmissione = "FPR12" if len(self.commercial_partner_id.l10n_it_pa_index or '1') == 6: formato_trasmissione = "FPA12" if self.move_type == 'out_invoice': document_type = 'TD01' elif self.move_type == 'out_refund': document_type = 'TD04' else: document_type = 'TD0X' pdf = self.env.ref('account.account_invoices')._render_qweb_pdf(self.id)[0] pdf = base64.b64encode(pdf) pdf_name = re.sub(r'\W+', '', self.name) + '.pdf' # Create file content. template_values = { 'record': self, 'format_date': format_date, 'format_monetary': format_monetary, 'format_numbers': format_numbers, 'format_numbers_two': format_numbers_two, 'format_phone': format_phone, 'discount_type': discount_type, 'get_vat_number': get_vat_number, 'get_vat_country': get_vat_country, 'abs': abs, 'formato_trasmissione': formato_trasmissione, 'document_type': document_type, 'pdf': pdf, 'pdf_name': pdf_name, } content = self.env.ref('l10n_it_edi.account_invoice_it_FatturaPA_export')._render(template_values) return content def send_pec_mail(self): self.ensure_one() allowed_state = ['to_send', 'invalid'] if ( not self.company_id.l10n_it_mail_pec_server_id or not self.company_id.l10n_it_mail_pec_server_id.active or not self.company_id.l10n_it_address_send_fatturapa ): self.message_post( body=(_("Error when sending mail with E-Invoice: Your company must have a mail PEC server and must indicate the mail PEC that will send electronic invoice.")) ) self.l10n_it_send_state = 'invalid' return if self.l10n_it_send_state not in allowed_state: raise UserError(_("%s isn't in a right state. It must be in a 'Not yet send' or 'Invalid' state.") % (self.display_name)) message = self.env['mail.message'].create({ 'subject': _('Sending file: %s') % (self.l10n_it_einvoice_id.name), 'body': _('Sending file: %s to ES: %s') % (self.l10n_it_einvoice_id.name, self.env.company.l10n_it_address_recipient_fatturapa), 'author_id': self.env.user.partner_id.id, 'email_from': self.env.company.l10n_it_address_send_fatturapa, 'reply_to': self.env.company.l10n_it_address_send_fatturapa, 'mail_server_id': self.env.company.l10n_it_mail_pec_server_id.id, 'attachment_ids': [(6, 0, self.l10n_it_einvoice_id.ids)], }) mail_fattura = self.env['mail.mail'].sudo().with_context(wo_bounce_return_path=True).create({ 'mail_message_id': message.id, 'email_to': self.env.company.l10n_it_address_recipient_fatturapa, }) try: mail_fattura.send(raise_exception=True) self.message_post( body=(_("Mail sent on %s by %s") % (fields.Datetime.now(), self.env.user.display_name)) ) self.l10n_it_send_state = 'sent' except MailDeliveryException as error: self.message_post( body=(_("Error when sending mail with E-Invoice: %s") % (error.args[0])) ) self.l10n_it_send_state = 'invalid' def _import_xml_invoice(self, tree): ''' Extract invoice values from the E-Invoice xml tree passed as parameter. :param content: The tree of the xml file. :return: A dictionary containing account.invoice values to create/update it. ''' invoices = self.env['account.move'] multi = False # possible to have multiple invoices in the case of an invoice batch, the batch itself is repeated for every invoice of the batch for body_tree in tree.xpath('//FatturaElettronicaBody'): if multi: # make sure all the iterations create a new invoice record (except the first which could have already created one) self = self.env['account.move'] multi = True elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento') if elements and elements[0].text and elements[0].text == 'TD01': self_ctx = self.with_context(default_move_type='in_invoice') elif elements and elements[0].text and elements[0].text == 'TD04': self_ctx = self.with_context(default_move_type='in_refund') else: _logger.info(_('Document type not managed: %s.') % (elements[0].text)) # type must be present in the context to get the right behavior of the _default_journal method (account.move). # journal_id must be present in the context to get the right behavior of the _default_account method (account.move.line). elements = tree.xpath('//CessionarioCommittente//IdCodice') company = elements and self.env['res.company'].search([('vat', 'ilike', elements[0].text)], limit=1) if not company: elements = tree.xpath('//CessionarioCommittente//CodiceFiscale') company = elements and self.env['res.company'].search([('l10n_it_codice_fiscale', 'ilike', elements[0].text)], limit=1) if company: self_ctx = self_ctx.with_context(company_id=company.id) else: company = self.env.company if elements: _logger.info(_('Company not found with codice fiscale: %s. The company\'s user is set by default.') % elements[0].text) else: _logger.info(_('Company not found. The company\'s user is set by default.')) if not self.env.is_superuser(): if self.env.company != company: raise UserError(_("You can only import invoice concern your current company: %s") % self.env.company.display_name) # Refund type. # TD01 == invoice # TD02 == advance/down payment on invoice # TD03 == advance/down payment on fee # TD04 == credit note # TD05 == debit note # TD06 == fee elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento') if elements and elements[0].text and elements[0].text == 'TD01': type = 'in_invoice' elif elements and elements[0].text and elements[0].text == 'TD04': type = 'in_refund' # self could be a single record (editing) or be empty (new). with Form(self.with_context(default_move_type=type)) as invoice_form: message_to_log = [] # Partner (first step to avoid warning 'Warning! You must first select a partner.'). <1.2> elements = tree.xpath('//CedentePrestatore//IdCodice') partner = elements and self.env['res.partner'].search(['&', ('vat', 'ilike', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1) if not partner: elements = tree.xpath('//CedentePrestatore//CodiceFiscale') partner = elements and self.env['res.partner'].search(['&', ('l10n_it_codice_fiscale', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1) if not partner: elements = tree.xpath('//DatiTrasmissione//Email') partner = elements and self.env['res.partner'].search(['&', '|', ('email', '=', elements[0].text), ('l10n_it_pec_email', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1) if partner: invoice_form.partner_id = partner else: message_to_log.append("%s<br/>%s" % ( _("Vendor not found, useful informations from XML file:"), self._compose_info_message( tree, './/CedentePrestatore'))) # Numbering attributed by the transmitter. <1.1.2> elements = tree.xpath('//ProgressivoInvio') if elements: invoice_form.payment_reference = elements[0].text elements = body_tree.xpath('.//DatiGeneraliDocumento//Numero') if elements: invoice_form.ref = elements[0].text # Currency. <2.1.1.2> elements = body_tree.xpath('.//DatiGeneraliDocumento/Divisa') if elements: currency_str = elements[0].text currency = self.env.ref('base.%s' % currency_str.upper(), raise_if_not_found=False) if currency != self.env.company.currency_id and currency.active: invoice_form.currency_id = currency # Date. <2.1.1.3> elements = body_tree.xpath('.//DatiGeneraliDocumento/Data') if elements: date_str = elements[0].text date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT) invoice_form.invoice_date = date_obj.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT) # Dati Bollo. <2.1.1.6> elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiBollo/ImportoBollo') if elements: invoice_form.l10n_it_stamp_duty = float(elements[0].text) # List of all amount discount (will be add after all article to avoid to have a negative sum) discount_list = [] percentage_global_discount = 1.0 # Global discount. <2.1.1.8> discount_elements = body_tree.xpath('.//DatiGeneraliDocumento/ScontoMaggiorazione') total_discount_amount = 0.0 if discount_elements: for discount_element in discount_elements: discount_line = discount_element.xpath('.//Tipo') discount_sign = -1 if discount_line and discount_line[0].text == 'SC': discount_sign = 1 discount_percentage = discount_element.xpath('.//Percentuale') if discount_percentage and discount_percentage[0].text: percentage_global_discount *= 1 - float(discount_percentage[0].text)/100 * discount_sign discount_amount_text = discount_element.xpath('.//Importo') if discount_amount_text and discount_amount_text[0].text: discount_amount = float(discount_amount_text[0].text) * discount_sign * -1 discount = {} discount["seq"] = 0 if discount_amount < 0: discount["name"] = _('GLOBAL DISCOUNT') else: discount["name"] = _('GLOBAL EXTRA CHARGE') discount["amount"] = discount_amount discount["tax"] = [] discount_list.append(discount) # Comment. <2.1.1.11> elements = body_tree.xpath('.//DatiGeneraliDocumento//Causale') for element in elements: invoice_form.narration = '%s%s\n' % (invoice_form.narration or '', element.text) # Informations relative to the purchase order, the contract, the agreement, # the reception phase or invoices previously transmitted # <2.1.2> - <2.1.6> for document_type in ['DatiOrdineAcquisto', 'DatiContratto', 'DatiConvenzione', 'DatiRicezione', 'DatiFattureCollegate']: elements = body_tree.xpath('.//DatiGenerali/' + document_type) if elements: for element in elements: message_to_log.append("%s %s<br/>%s" % (document_type, _("from XML file:"), self._compose_info_message(element, '.'))) # Dati DDT. <2.1.8> elements = body_tree.xpath('.//DatiGenerali/DatiDDT') if elements: message_to_log.append("%s<br/>%s" % ( _("Transport informations from XML file:"), self._compose_info_message(body_tree, './/DatiGenerali/DatiDDT'))) # Due date. <2.4.2.5> elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento') if elements: date_str = elements[0].text date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT) invoice_form.invoice_date_due = fields.Date.to_string(date_obj) # Total amount. <2.4.2.6> elements = body_tree.xpath('.//ImportoPagamento') amount_total_import = 0 for element in elements: amount_total_import += float(element.text) if amount_total_import: message_to_log.append(_("Total amount from the XML File: %s") % ( amount_total_import)) # Bank account. <2.4.2.13> if invoice_form.type not in ('out_invoice', 'in_refund'): elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/IBAN') if elements: if invoice_form.partner_id and invoice_form.partner_id.commercial_partner_id: bank = self.env['res.partner.bank'].search([ ('acc_number', '=', elements[0].text), ('partner_id.id', '=', invoice_form.partner_id.commercial_partner_id.id) ]) else: bank = self.env['res.partner.bank'].search([('acc_number', '=', elements[0].text)]) if bank: invoice_form.partner_bank_id = bank else: message_to_log.append("%s<br/>%s" % ( _("Bank account not found, useful informations from XML file:"), self._compose_multi_info_message( body_tree, ['.//DatiPagamento//Beneficiario', './/DatiPagamento//IstitutoFinanziario', './/DatiPagamento//IBAN', './/DatiPagamento//ABI', './/DatiPagamento//CAB', './/DatiPagamento//BIC', './/DatiPagamento//ModalitaPagamento']))) else: elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento') if elements: message_to_log.append("%s<br/>%s" % ( _("Bank account not found, useful informations from XML file:"), self._compose_info_message(body_tree, './/DatiPagamento'))) # Invoice lines. <2.2.1> elements = body_tree.xpath('.//DettaglioLinee') if elements: for element in elements: with invoice_form.invoice_line_ids.new() as invoice_line_form: # Sequence. line_elements = element.xpath('.//NumeroLinea') if line_elements: invoice_line_form.sequence = int(line_elements[0].text) * 2 # Product. line_elements = element.xpath('.//Descrizione') if line_elements: invoice_line_form.name = " ".join(line_elements[0].text.split()) elements_code = element.xpath('.//CodiceArticolo') if elements_code: for element_code in elements_code: type_code = element_code.xpath('.//CodiceTipo')[0] code = element_code.xpath('.//CodiceValore')[0] if type_code.text == 'EAN': product = self.env['product.product'].search([('barcode', '=', code.text)]) if product: invoice_line_form.product_id = product break if partner: product_supplier = self.env['product.supplierinfo'].search([('name', '=', partner.id), ('product_code', '=', code.text)]) if product_supplier and product_supplier.product_id: invoice_line_form.product_id = product_supplier.product_id break if not invoice_line_form.product_id: for element_code in elements_code: code = element_code.xpath('.//CodiceValore')[0] product = self.env['product.product'].search([('default_code', '=', code.text)]) if product: invoice_line_form.product_id = product break # Price Unit. line_elements = element.xpath('.//PrezzoUnitario') if line_elements: invoice_line_form.price_unit = float(line_elements[0].text) # Quantity. line_elements = element.xpath('.//Quantita') if line_elements: invoice_line_form.quantity = float(line_elements[0].text) else: invoice_line_form.quantity = 1 # Taxes tax_element = element.xpath('.//AliquotaIVA') natura_element = element.xpath('.//Natura') invoice_line_form.tax_ids.clear() if tax_element and tax_element[0].text: percentage = float(tax_element[0].text) if natura_element and natura_element[0].text: l10n_it_kind_exoneration = natura_element[0].text tax = self.env['account.tax'].search([ ('company_id', '=', invoice_form.company_id.id), ('amount_type', '=', 'percent'), ('type_tax_use', '=', 'purchase'), ('amount', '=', percentage), ('l10n_it_kind_exoneration', '=', l10n_it_kind_exoneration), ], limit=1) else: tax = self.env['account.tax'].search([ ('company_id', '=', invoice_form.company_id.id), ('amount_type', '=', 'percent'), ('type_tax_use', '=', 'purchase'), ('amount', '=', percentage), ], limit=1) l10n_it_kind_exoneration = '' if tax: invoice_line_form.tax_ids.add(tax) else: if l10n_it_kind_exoneration: message_to_log.append(_("Tax not found with percentage: %s and exoneration %s for the article: %s") % ( percentage, l10n_it_kind_exoneration, invoice_line_form.name)) else: message_to_log.append(_("Tax not found with percentage: %s for the article: %s") % ( percentage, invoice_line_form.name)) # Discount in cascade mode. # if 3 discounts : -10% -50€ -20% # the result must be : (((price -10%)-50€) -20%) # Generic form : (((price -P1%)-A1€) -P2%) # It will be split in two parts: fix amount and pourcent amount # example: (((((price - A1€) -P2%) -A3€) -A4€) -P5€) # pourcent: 1-(1-P2)*(1-P5) # fix amount: A1*(1-P2)*(1-P5)+A3*(1-P5)+A4*(1-P5) (we must take account of all # percentage present after the fix amount) line_elements = element.xpath('.//ScontoMaggiorazione') total_discount_amount = 0.0 total_discount_percentage = percentage_global_discount if line_elements: for line_element in line_elements: discount_line = line_element.xpath('.//Tipo') discount_sign = -1 if discount_line and discount_line[0].text == 'SC': discount_sign = 1 discount_percentage = line_element.xpath('.//Percentuale') if discount_percentage and discount_percentage[0].text: pourcentage_actual = 1 - float(discount_percentage[0].text)/100 * discount_sign total_discount_percentage *= pourcentage_actual total_discount_amount *= pourcentage_actual discount_amount = line_element.xpath('.//Importo') if discount_amount and discount_amount[0].text: total_discount_amount += float(discount_amount[0].text) * discount_sign * -1 # Save amount discount. if total_discount_amount != 0: discount = {} discount["seq"] = invoice_line_form.sequence + 1 if total_discount_amount < 0: discount["name"] = _('DISCOUNT: ') + invoice_line_form.name else: discount["name"] = _('EXTRA CHARGE: ') + invoice_line_form.name discount["amount"] = total_discount_amount discount["tax"] = [] for tax in invoice_line_form.tax_ids: discount["tax"].append(tax) discount_list.append(discount) invoice_line_form.discount = (1 - total_discount_percentage) * 100 # Apply amount discount. for discount in discount_list: with invoice_form.invoice_line_ids.new() as invoice_line_form_discount: invoice_line_form_discount.tax_ids.clear() invoice_line_form_discount.sequence = discount["seq"] invoice_line_form_discount.name = discount["name"] invoice_line_form_discount.price_unit = discount["amount"] new_invoice = invoice_form.save() new_invoice.l10n_it_send_state = "other" elements = body_tree.xpath('.//Allegati') if elements: for element in elements: name_attachment = element.xpath('.//NomeAttachment')[0].text attachment_64 = str.encode(element.xpath('.//Attachment')[0].text) attachment_64 = self.env['ir.attachment'].create({ 'name': name_attachment, 'datas': attachment_64, 'type': 'binary', }) # default_res_id is had to context to avoid facturx to import his content # no_new_invoice to prevent from looping on the message_post that would create a new invoice without it new_invoice.with_context(no_new_invoice=True, default_res_id=new_invoice.id).message_post( body=(_("Attachment from XML")), attachment_ids=[attachment_64.id] ) for message in message_to_log: new_invoice.message_post(body=message) invoices += new_invoice return invoices def _compose_info_message(self, tree, element_tags): output_str = "" elements = tree.xpath(element_tags) for element in elements: output_str += "<ul>" for line in element.iter(): if line.text: text = " ".join(line.text.split()) if text: output_str += "<li>%s: %s</li>" % (line.tag, text) output_str += "</ul>" return output_str def _compose_multi_info_message(self, tree, element_tags): output_str = "<ul>" for element_tag in element_tags: elements = tree.xpath(element_tag) if not elements: continue for element in elements: text = " ".join(element.text.split()) if text: output_str += "<li>%s: %s</li>" % (element.tag, text) return output_str + "</ul>" @api.model def _get_xml_decoders(self): # Override ubl_decoders = [('Italy', self._detect_italy_edi, self._import_xml_invoice)] return super(AccountMove, self)._get_xml_decoders() + ubl_decoders @api.model def _detect_italy_edi(self, tree, file_name): # Quick check the file name looks like the one of an Italian EDI XML. flag = re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", file_name) error = None return {'flag': flag, 'error': error}
class MonthPlan(models.Model): ''' 月计划, 用计划计算的时候才将年计划的数据拿出来,然后更新. 月计划重新计算规则, 找到最后执行的计划,之后的以此作为历史记录,然后进行偏移。 ''' _name = 'metro_park_maintenance.month_plan' _description = '月计划,月计划中添加里程修' _rec_name = 'plan_name' _track_log = True _order = "year desc, month desc" year_plan = fields.Many2one( string="所属年计划", comodel_name="metro_park_maintenance.year_plan", ondelete="restrict", help="必需要月计划删除了以后年计划才能删除," "防止不小心删除年计划把所有的数据都删除了") year = fields.Integer(string="年") month = fields.Integer(string="月") start_date = fields.Date(string="开始时间", compute="compute_date", store=True, help='这个字段用于搜索') end_date = fields.Date(string="结束时间", compute="compute_date", store=True, help='这个字段用于搜索') pms_work_class_info = fields.Many2one(comodel_name='pms.department', string='工班') def get_default_sequence(self): ''' 取得默认的序号 :return: ''' return self.env['ir.sequence'].next_by_code('month.plan.number') plan_no = fields.Char(string='计划编号') plan_name = fields.Char(string='计划名称') state = fields.Selection(string='状态', selection=[('draft', '草稿'), ('published', '发布')], default='draft') operation_buttons = fields.Char(string='操作') plan_datas = fields.Many2many( string="月计划数据", comodel_name="metro_park_maintenance.plan_data", relation="month_plan_and_plan_data_rel", column1="plan_id", column2="data_id") remark = fields.Char(string='备注') active = fields.Boolean(default=True) _sql_constraints = [('plan_no_unique', 'UNIQUE(plan_no)', "计划编号不能重复")] @api.one @api.constrains('plan_name') def _check_plan_name(self): ''' 检查年计划名称 :return: ''' records = self.search([('plan_name', '=', self.plan_name), ('id', '!=', self.id)]) if records: raise exceptions.ValidationError("年计划名称重复,请选用其它名称!") # 这里实际上显示年月用 @api.one @api.depends('year', 'month') def compute_date(self): ''' 计算日期 :return: ''' if self.month and self.year: tmp_date = pendulum.Date(int(self.year), int(self.month), 1) self.start_date = tmp_date.format('YYYY-MM-DD') days = tmp_date.days_in_month end_date = tmp_date.add(days=days - 1) self.end_date = end_date.format('YYYY-MM-DD') @api.multi def publish_plan(self): ''' 发布计划 ''' self.ensure_one() self.state = 'published' # 相关的数据设置为published rule_infos = self.env["metro_park_maintenance.rule_info"] \ .search([("month", "=", self.month), ("year", "=", self.year), ("data_source", "=", "month")]) for info in rule_infos: if not info.work_class and not info.pms_work_class: pass # raise exceptions.Warning("没有安排工班, 无法发布!") rule_infos.write({"state": "published"}) self.env['metro_park_maintenance.plan_log'] \ .sudo() \ .add_plan_log('{year}年{month},月计划发布成功!' .format(year=self.year, month=self.month)) config = self.env['metro_park_base.system_config'].get_configs() use_pms_maintaince = config.get('start_pms', False) try: if use_pms_maintaince == 'yes': self.env['mdias_pms_interface'].sudo( ).year_month_week_day_plan(self, 'M', '1') except Exception as error: _logger.error('PMS接口获取失败{error}'.format(error=error)) @api.multi def reback_plan(self): ''' 撤回计划 :return: ''' year = self.year month = self.month date_start = pendulum.Date(year, month, 1) date_start_str = date_start.format('YYYY-MM-DD') date_end_str = pendulum.Date(year, month, date_start.days_in_month) \ .format('YYYY-MM-DD') # 查询当前是否已经有发布了的周计划 week_plans = self.env['metro_park_maintenance.week_plan'] \ .search(['|', '&', ('start_date', '>=', date_start_str), ('start_date', '<=', date_end_str), '&', ('end_date', '>=', date_start_str), ('end_date', '<=', date_end_str), ('state', '=', 'published')]) if len(week_plans) > 0: raise exceptions.ValidationError('当前月份有周计划已经发布,无法撤回,请先撤回相应的周计划!') self.state = 'draft' try: config = self.env['metro_park_base.system_config'].get_configs() use_pms_maintaince = config.get('start_pms', False) except Exception as e: _logger.error('PMS基础信息配置错误{error}'.format(error=e)) try: if use_pms_maintaince == 'yes': self.env['mdias_pms_interface'].sudo( ).year_month_week_day_plan(self, 'M', '3') except Exception as error: _logger.error('PMS接口获取失败{error}'.format(error=error)) @api.multi def view_month_plan_action(self): ''' 取得月计划 :return: ''' return { "type": "ir.actions.client", "name": "{year}_{month}".format(year=self.year, month=self.month), "tag": "month_month_plan_editor", "context": { "month_plan_id": self.id } } @api.multi def view_month_plan_works(self): ''' 查看生产作业大表 :return: ''' return { "type": "ir.actions.client", "name": "生产作业{year}_{month}".format(year=self.year, month=self.month), "tag": "month_plan_works", "context": { "month_plan_id": self.id } } @api.multi def view_month_plan_works_import(self): form_id = self.env.ref( "metro_park_maintenance.view_month_plan_works_import").id return { 'type': 'ir.actions.act_window', 'views': [[form_id, 'form']], 'view_mode': 'form', 'res_model': "metro_park_maintenance.month_plan_import_wizard", 'target': 'new', } @api.multi def month_plan_export(self): """ 导出月计划 :return: """ return { 'name': '月计划下载', 'type': 'ir.actions.act_url', 'url': '/export_month_plan/{month_plan_id}'.format(month_plan_id=self.id) } @api.multi def unlink(self): ''' 必需要周计划删除以后才能删除月计划 :return: ''' for record in self: start_date = record.start_date end_date = record.end_date records = self.env["metro_park_maintenance.week_plan"] \ .search(['|', '&', ('start_date', '>=', start_date), ('start_date', '<=', end_date), '&', ('end_date', '>=', start_date), ('end_date', '<=', end_date)]) if len(records) > 0: raise exceptions.ValidationError( "{month}还有相应的计划没有删除".format(month=record.month)) for record in self: # 先删除具体信息 records = self.env["metro_park_maintenance.rule_info"] \ .search([('date', '>=', record.start_date), ('date', '<=', record.end_date), ('data_source', '=', 'month')]) records.write({"active": False}) # 对应用很多设备, 只有具体信息删除完了以后才删除计划数据 records = self.env["metro_park_maintenance.plan_data"] \ .search([("date", '>=', record.start_date), ("date", "<=", record.end_date)]) for tmp_record in records: if tmp_record.rule_infos: tmp_record.write({"active": False}) @api.multi def unlink(self): ''' 删除月计划, 改成软删除 :return: ''' for record in self: # 删除月计划数据。 records = self.env["metro_park_maintenance.rule_info"] \ .search([("plan_id", '=', 'metro_park_maintenance.month_plan, {plan_id}' .format(plan_id=record.id))]) records.write({"active": False}) self.write({"active": False}) @api.multi def clear_month_info(self): ''' 删除当月的月计划产生的数据 :return: ''' for record in self: # 删除月计划数据。 records = self.env["metro_park_maintenance.rule_info"] \ .search([("plan_id", '=', 'metro_park_maintenance.month_plan, {plan_id}' .format(plan_id=record.id))]) records.write({"active": False}) @api.multi def import_month_works(self): ''' 导入生产作业 :return: ''' return { "type": "ir.actions.act_window", "res_model": "metro_park_maintenance.import_month_works", 'view_mode': 'form', 'target': 'new', 'context': { "month_plan_id": self.id, }, "views": [[ self.env.ref( 'metro_park_maintenance.import_month_works_form').id, "form" ]] } @api.multi def re_compute_plan(self): ''' 校正月计划,如果历史记划没有执行则进行校正, 扣车等要自动移动检修 :return: ''' rule_infos = self.env["metro_park_maintenance.rule_info"]\ .search([[('year', '=', self.year), ('month', '=', self.month), ('rule_type', '=', self.month)]]) return rule_infos @api.multi def get_plan_work_class_data(self): ''' 安排工班, 工班选择按照轮换的方式,要求任务均衡, :return: ''' department_property_work_class = \ self.env.ref("metro_park_base.department_property_balance_work_class").id # 取得有均衡修工班属性的工班 work_classes = self.env["funenc.wechat.department"].search([ ("properties", "in", [department_property_work_class]) ]) work_class_count = len(work_classes) # 取得所有的任务, 由于数据复制了一份,所以这里还是要限制计划id works = self.env["metro_park_maintenance.rule_info"].search( [("rule.target_plan_type", "in", ["year", "month"]), ('date', '>=', str(self.start_date)), ('date', '<=', str(self.end_date)), ('plan_id', '=', 'metro_park_maintenance.month_plan, {plan_id}'.format( plan_id=self.id))], order="dev asc, date asc") tasks = [] date_tasks = {} pre_task = None for work in works: # 默认都有第几天,导入的时候要处理下 task_index = len(tasks) task = { "index": task_index, "date": str(work.date), "dev": work.dev.id, "val": 0, "rule_id": work.rule.id, "need_platform": True if 'D' in work.rule_no else False, "same_with_pre_task": -1, "id": work.id } if pre_task: pre_date = pendulum.parse(pre_task["date"]).add(days=1) cur_date = pendulum.parse(task["date"]) if pre_date == cur_date \ and pre_task["rule_id"] == task["rule_id"] \ and pre_task["dev"] == task["dev"]: task["same_with_pre_task"] = pre_task["index"] tasks.append(task) pre_task = task date_tasks.setdefault(str(work.date), []).append(task_index) # 统计每个地方的工班数量, 原则上一个工班只属于一个地方 location_work_class_info = {} for work_class in work_classes: if len(work_class.locations) > 0: location = work_class.locations[0] location_work_class_info.setdefault(location.id, []).append(work_class.id) # 取得本月的天数 start_date = pendulum.parse(str(self.start_date)) # 取得服务器配置 config = self.env["metro_park_base.system_config"].get_configs() return { "work_classes": work_classes.mapped("id"), "work_class_count": work_class_count, "tasks": tasks, "tasks_count": len(tasks), "location_work_class_info": location_work_class_info, "day_count": start_date.days_in_month, "date_tasks": date_tasks, "calc_host": config.get("calc_host") } @api.model def deal_plan_work_class_result(self, plan_data, rst): ''' 处理计划结果数据, 将工班结果写入 :return: ''' rst = rst["datas"] tasks = plan_data["tasks"] work_classes = plan_data["work_classes"] for index, task in enumerate(tasks): rule_info = self.env["metro_park_maintenance.rule_info"]\ .browse(task["id"]) val = rst[index] _logger.info('the val is:{val}'.format(val=val)) rule_info.work_class = [(6, 0, [work_classes[val - 1]])] @api.multi def get_month_plan_data(self, wizard_id): ''' 计算月计划,本月原来安排的只能优选选择 :return: ''' wizard_info = \ self.env["metro_park_maintenance.month_plan_compute_wizard"].browse(wizard_id) # 当月的计划, 有可能是扣车移动过来的,同时可能有月计划的内容 real_infos = self.env["metro_park_maintenance.rule_info"]\ .search([('plan_id', '=', 'metro_park_maintenance.month_plan, {plan_id}'.format( plan_id=self.id)), ('rule.target_plan_type', '=', 'year')], order="date asc") # 年计划就要求均衡 dev_tasks_cache = dict() for info in real_infos: dev_tasks_cache.setdefault(info.dev.id, []).append(info) # 暂时没有考虑跨月的项,跨月的项要要作预先安排 # for key in dev_tasks_cache: # infos = dev_tasks_cache[key] # if len(infos) > 0 and infos[0].repair_num != 1: # while len(infos) > 0 and infos[0].repair_num != 1: # infos = infos[1:] # dev_tasks_cache[key] = infos month_start = pendulum.date(self.year, self.month, 1) month_days = month_start.days_in_month # 白名单 month_end = month_start.add(days=month_days) white_list = self.env["metro_park_maintenance.white_list"]\ .search([("date", ">=", month_start.format('YYYY-MM-DD')), ('date', '<=', month_end.format('YYYY-MM-DD'))]) white_dates = [] for record in white_list: white_dates.append( pendulum.parse(str(record.date)).format("YYYY-MM-DD")) # 查询特殊日期配置, 黑名单 special_days = \ self.env['metro_park_maintenance.holidays'].search([]) special_days_cache = { str(record.date): True for record in special_days } # 节假日 holidays = [] for tmp_index in range(1, month_days + 1): tmp_date = pendulum.date(self.year, self.month, tmp_index) tmp_date_str = tmp_date.format('YYYY-MM-DD') if (tmp_date.day_of_week == 6 or tmp_date.day_of_week == 0) \ and tmp_date_str not in white_dates: holidays.append(tmp_index) if tmp_date_str in special_days_cache and tmp_date_str not in white_dates: holidays.append(tmp_index) # 取得缓存信息, 扣车等会自动挪动,或是用户手动移动 month_start_date = pendulum.date(self.year, self.month, 1) history_plans = self.env["metro_park_maintenance.rule_info"]\ .get_year_plan_history_info(month_start_date, False) history_plan_cache = {plan.dev.id: plan for plan in history_plans} tasks = [] for dev_id in dev_tasks_cache: dev_tasks = [] infos = dev_tasks_cache[dev_id] pre_info = None for tmp_index, info in enumerate(infos): # 过滤掉复复的情况 if pre_info and pre_info.rule.id == info.rule.id: pre_info = info continue else: pre_info = info rule = info.rule task = { "index": len(tasks), "id": info.id, "rule_id": info.rule.id, "dev": dev_id, # 同一设备不限定 "pre_index": len(tasks) - 1 if tmp_index != 0 else -1, "period": info.rule.period, "positive_offset": info.rule.positive_offset, "negative_offset": info.rule.negative_offset, "repeat_index": -1, # 检修第一天 "repair_day": 1, "repair_days": info.rule.repair_days, "left_days": info.rule.period, "info_count": len(infos), "month_start_val": 1, "month_end_val": month_days, "repeat": False, "repair_num": info.repair_num, "attach_index": -1, "is_extra": False } # 第一个月需要考虑历史信息, 但是这个不考虑修次,决定了最终的位置 if tmp_index == 0: history_repair_info = history_plan_cache.get(dev_id, False) if history_repair_info: # 最后一个计划开始的日期 tmp_date = pendulum.parse( str(history_repair_info["date"])) tmp_delta = month_start_date - tmp_date # 已经使用了的天数 offset_days = tmp_delta.days - 1 # 已经超超期,需要直接安排 if offset_days > rule['period'] + rule[ 'positive_offset']: task['left_days'] = 0 task['positive_offset'] = 0 task['negative_offset'] = 0 # 在负区间内 elif rule['period'] - rule[ 'negative_offset'] <= offset_days < rule[ 'period']: task['left_days'] = rule['period'] - offset_days task['negative_offset'] = rule[ 'period'] - offset_days # 在正区间内, 这个有可能刚好剩下的就是星期六和星期天,这个就有点尴尬了 elif rule['period'] <= offset_days <= rule[ 'period'] + rule['positive_offset']: task['left_days'] = 0 task['negative_offset'] = 0 task['positive_offset'] = \ rule['period'] + rule['positive_offset'] - offset_days else: # 还没有到达检修周期, 肯定是在上个月进行的,所以无论如何都可以排 task['left_days'] = rule['period'] - offset_days tasks.append(task) dev_tasks.append(task) # 时于按时间排序,均衡修不可能同一天进行多个,所以,后面的几个正好是重复的 repair_days = rule.repair_days is_extra = False for day in range(1, repair_days): tmp_task = copy.copy(task) # 跨月的情况, 从下月找到相应的数据 if tmp_index + day > len(infos) - 1: is_extra = True tmp_year = self.year next_month = self.month + 1 if next_month > 12: next_month = 1 tmp_year += 1 left_rule_infos = \ self.env["metro_park_maintenance.rule_info"].search( [('year', '=', tmp_year), ('month', "=", next_month), ('dev', '=', dev_id), ('rule', '=', rule.id)], order="date asc") for tmp_info in left_rule_infos: infos.append(tmp_info) # assert infos[tmp_index + day].rule.id == info.rule.id # assert infos[tmp_index + day].id != info.id tmp_info = infos[tmp_index + day] tmp_task["id"] = tmp_info.id tmp_task["repeat_index"] = len(tasks) - 1 tmp_task["pre_index"] = len(tasks) - 1 tmp_task["repair_day"] = day + 1 tmp_task["index"] = len(tasks) tmp_task["repeat"] = True tmp_task["is_extra"] = is_extra tasks.append(tmp_task) # 取得计算服务器 config = self.env['metro_park_base.system_config'].get_configs() calc_host = config.get('calc_host', False) attach_tasks = [] # 附加空调专检的task 如果是第一月份的话, 第一个月尽量安排 plan_kt_month_1 = self.env.ref("metro_park_base.plan_kt_month_1").id plan_kt_month_2 = self.env.ref("metro_park_base.plan_kt_month_2").id if wizard_info.plan_kt \ and wizard_info.plan_kt_month == plan_kt_month_1: attach_tasks = [] for task in tasks: # 只按排修程只有一天的 if task["repair_days"] == 1 and task["repair_day"] == 1: attach_task = { "index": len(attach_tasks), "attach_index": task["index"], "force": False, "dev_id": task['dev'] } # 相互关联 task["attach_index"] = attach_task["index"] repair_num = task["repair_num"] # 下一次没法进行的情况 next_repair_info = \ self.env['metro_park_maintenance.plan_config_data'].get_next_repair_info(repair_num) if next_repair_info.repair_days > 1: attach_task["force"] = True attach_tasks.append(attach_task) elif wizard_info.plan_kt \ and wizard_info.plan_kt_month.id == plan_kt_month_2: # 如果上一个月没有安排空调专检的话这个月就必需要安排 kt_rule_id = self.env.ref( "metro_park_base_data_10.repair_rule_kt").id prev_month = self.month - 1 prev_year = self.year if prev_month <= 0: prev_month = 12 prev_year = prev_year - 1 his_kt_infos = self.env["metro_park_maintenance.rule_info"].search( [("rule", '=', kt_rule_id), ("month", "=", prev_month), ("year", '=', prev_year)]) his_kt_info_cache = dict() for tmp_info in his_kt_infos: his_kt_info_cache[tmp_info.dev.id] = tmp_info black_dev = [] for task in tasks: if task["dev"] not in his_kt_info_cache \ and task['repair_day'] == 1 and task["dev"] not in black_dev: black_dev.append(task['dev']) attach_task = { "index": len(attach_tasks), "attach_index": task["index"], "force": False, "dev_id": task['dev'] } # 相互关联 task["attach_index"] = len(attach_tasks) attach_tasks.append(attach_task) return { "tasks": tasks, "month": self.month, "year": self.year, "month_days": month_days, "holidays": holidays, "calc_host": calc_host, "max_plan_per_day": wizard_info.max_plan_per_day, "attach_tasks": attach_tasks or [], "plan_kt": True if wizard_info.plan_kt.value == 'yes' else False, "plan_kt_month": 1 if wizard_info.plan_kt_month.id == plan_kt_month_1 else 2 } @api.multi def deal_month_plan_data(self, plan_data, result): ''' 处理月计划数据, 只是调整计划时间 :return: ''' if result['status'] != 200: raise exceptions.ValidationError("计算错错,未能找正确解") result = result['datas'] plan_result = result["plan_result"] year = plan_data["year"] month = plan_data["month"] month_start = pendulum.date(year, month, 1) plan_id = self.id kt_rule_id = self.env.ref("metro_park_base_data_10.repair_rule_kt").id vals = [] attach_result = result['attach_result'] tasks = plan_data['tasks'] for task in tasks: info_id = task["id"] index = task["index"] value = plan_result[index] # value是从1开始 plan_date = month_start.add(days=value - 1) record = self.env["metro_park_maintenance.rule_info"].browse( info_id) if not task["is_extra"]: record.write({ "date": plan_date.format("YYYY-MM-DD"), "year": plan_date.year, "month": plan_date.month, "day": plan_date.day_of_year }) else: # 下月放在本月的情况,创建本月数据 self.env["metro_park_maintenance.rule_info"].create([{ "date": plan_date.format("YYYY-MM-DD"), "dev": task['dev'], "year": plan_date.year, "month": plan_date.month, "day": plan_date.day, "plan_id": "metro_park_maintenance.month_plan, {plan_id}".format( plan_id=plan_id), "rule": task["rule_id"], 'data_source': 'month' }]) attach_index = task["attach_index"] if attach_index != -1 and attach_result[attach_index] == 1: plan_date = plan_date.subtract(days=1) vals.append({ "date": plan_date.format("YYYY-MM-DD"), "dev": task['dev'], "year": plan_date.year, "month": plan_date.month, "day": plan_date.day, "plan_id": "metro_park_maintenance.month_plan, {plan_id}".format( plan_id=plan_id), "rule": kt_rule_id, 'data_source': 'month' }) self.env["metro_park_maintenance.rule_info"].create(vals) @api.multi def view_delta(self): ''' 查看本月修程和上月的间隔 :return: ''' month = self.month year = self.year pre_year = year pre_month = month - 1 if month == 1: pre_year = year - 1 pre_month = 12 # 取得历修程信息, 取得后一天的 history_plan_cache = dict() pre_date = pendulum.date(pre_year, pre_month, 1) pre_date = pendulum.date(pre_year, pre_month, pre_date.days_in_month) pre_plans = self.env["metro_park_maintenance.rule_info"].search( [("date", '<', pre_date), ('year', '=', pre_year), ('data_source', '=', 'month')], order='date asc') for tmp_plan in pre_plans: history_plan_cache[tmp_plan.dev.id] = tmp_plan # 取得当月的信息 month_plans = self.env["metro_park_maintenance.rule_info"].search( [("year", '=', year), ('month', '=', month), ('data_source', '=', 'month')], order='date asc') cur_month_cache = {} for month_plan in month_plans: cur_month_cache.setdefault(month_plan.dev.id, []).append(month_plan) vals = [] for dev_id in cur_month_cache: plan = cur_month_cache[dev_id][0] tmp_end_date = pendulum.parse(str(plan.date)) if dev_id in history_plan_cache: his_plan = history_plan_cache.get(dev_id, None) if his_plan: tmp_start_date = pendulum.parse(str(his_plan.date)) delta = tmp_end_date - tmp_start_date vals.append({"dev": dev_id, "days": delta.days}) records = self.env[ "metro_park_maintenance.month_plan_delta_info"].create(vals) tree_id = self.env.ref( 'metro_park_maintenance.month_plan_delta_info_list').id return { "type": "ir.actions.act_window", "target": "new", "res_model": "metro_park_maintenance.month_plan_delta_info", "name": "计划偏移", "views": [[tree_id, 'list'], [False, 'form']], "domain": [("id", "in", records.ids)] } @api.multi def syn_year_info(self): ''' 同步年计划数据 :return: ''' # 清除自身的内容 infos = self.env["metro_park_maintenance.rule_info"].search([ ('plan_id', '=', 'metro_park_maintenance.month_plan, {plan_id}'.format( plan_id=self.id)) ]) infos.write({"active": False}) rule_infos = self.env["metro_park_maintenance.rule_info"].search([ ('data_source', '=', 'year'), ('month', '=', self.month), ('plan_id', '=', "metro_park_maintenance.year_plan, {plan_id}".format( plan_id=self.year_plan.id)) ]) vals = [] for info in rule_infos: vals.append({ 'dev': info.dev.id, 'rule': info.rule.id, 'data_source': 'month', 'date': str(info.date), 'parent_id': info.id, 'repair_num': info.repair_num, 'repair_day': info.repair_day, 'plan_id': 'metro_park_maintenance.month_plan, {plan_id}'.format( plan_id=self.id) }) return self.env["metro_park_maintenance.rule_info"] \ .create(vals) @api.model def export_produce_plan(self): """ 导出生产计划 :return: """ now_day = pendulum.today() pre_year = now_day.subtract(years=1) return { "type": "ir.actions.act_window", "res_model": "metro_park_maintenance.export_produce_plan_wizard", 'view_mode': 'form', "target": "new", 'context': { "default_start_year": self.env["metro_park_maintenance.year"].search( domain=[("val", "=", pre_year.year)]).id, "default_start_month": self.env["metro_park_maintenance.month"].search( domain=[("val", "=", pre_year.month)]).id, "default_end_year": self.env["metro_park_maintenance.year"].search( domain=[("val", "=", now_day.year)]).id, "default_end_month": self.env["metro_park_maintenance.month"].search( domain=[("val", "=", now_day.month)]).id, }, "views": [[ self.env.ref( 'metro_park_maintenance.export_produce_plan_wizard_form'). id, "form" ]] } @api.multi def clear_duplicate(self): ''' 去除一个月排了多个修程的情况 :return: ''' dev_rule_cache = {} records = self.env["metro_park_maintenance.rule_info"].search( [('plan_id', '=', 'metro_park_maintenance.month_plan, {plan_id}'.format( plan_id=self.id))], order='date asc') for record in records: dev_rule_cache.setdefault(record.dev.id, []).append(record.rule.id) # 去除一个月排了多个修程 del_info = [] for key in dev_rule_cache: rules = set(dev_rule_cache[key]) if len(rules) > 1: del_info.append({"dev_id": int(key), "rule": list(rules)[-1]}) # 数量不会太多,一个一个进行删除 for info in del_info: record = self.env["metro_park_maintenance.rule_info"].search([ ('dev', '=', info['dev_id']), ('rule.id', '=', info['rule']) ]) # 移动到下一个月去 record.unlink() @api.multi def import_work_class_info(self): ''' 导入生产说明, :return: ''' return { "type": "ir.actions.act_window", "res_model": "metro_park_base.import_produce_work_class", 'view_mode': 'form', 'target': 'new', 'context': { 'default_month_plan': self.id, }, "views": [[ self.env.ref( 'metro_park_maintenance.import_produce_work_class_form'). id, "form" ]] }
class ResPartnerBank(models.Model): _inherit = 'res.partner.bank' l10n_br_convenio_pagamento = fields.Char('Cód de Convênio Pagamento')
class SaleGenerator(models.Model): _name = 'sale.generator' name = fields.Char(string='Generator', default='/') partner_ids = fields.Many2many(comodel_name='res.partner', string="Partner") sale_ids = fields.One2many(comodel_name='sale.order', inverse_name='generator_id', string="Sales") tmpl_sale_id = fields.Many2one(comodel_name='sale.order', string="Sale Template", required=True, domain=[('is_template', '=', True)]) date_order = fields.Datetime(string='Date', oldname='date', default=fields.Datetime.now()) warehouse_id = fields.Many2one(comodel_name='stock.warehouse', required=True, string="Warehouse") state = fields.Selection([ ('draft', 'Draft'), ('generating', 'Generating Order'), ('done', 'Done'), ], string='State', readonly=True, default='draft') company_id = fields.Many2one(comodel_name='res.company', string="Company", related="warehouse_id.company_id", store=True) @api.multi def _prepare_copy_vals(self, partner): self.ensure_one() vals = { 'partner_id': partner.id, 'generator_id': self.id, 'warehouse_id': self.warehouse_id.id, 'company_id': self.warehouse_id.company_id.id, 'is_template': False, } return vals @api.multi def _create_order_for_partner(self, partner): self.ensure_one() vals = self._prepare_copy_vals(partner) self.tmpl_sale_id.copy(vals) @api.multi def button_update_order(self): for res in self: if not res.partner_ids: raise UserError( _("Can't generate sale order without customer ")) else: res.write({'state': 'generating'}) res._update_order() @api.multi def _update_order(self): self.ensure_one() partners_with_order = self.sale_ids.mapped('partner_id') for partner in self.partner_ids.filtered( lambda record: record not in partners_with_order): self._create_order_for_partner(partner) for sale in self.sale_ids.filtered( lambda record: record.partner_id not in self.partner_ids): sale.unlink() @api.multi def action_confirm(self): for res in self: res.write({'state': 'done'}) res.sale_ids.action_confirm() @api.multi def write(self, vals): res = super(SaleGenerator, self).write(vals) if 'partner_ids' in vals and self.state == 'generating': self._update_order() return res def add_generated_partner(self): return { 'type': 'ir.actions.act_window', 'res_model': 'res.partner', 'name': u"New Customer", 'id': self.env.ref('base.view_partner_form').id, 'view_mode': 'form', 'target': 'new', } @api.model def create(self, vals): if vals.get('name', '/') == '/': vals['name'] = self.env['ir.sequence'].next_by_code( 'sale.order.generator') or '/' return super(SaleGenerator, self).create(vals)
class HrEmployeeBase(models.AbstractModel): _inherit = "hr.employee.base" leave_manager_id = fields.Many2one( 'res.users', string='Time Off', compute='_compute_leave_manager', store=True, readonly=False, help="User responsible of leaves approval.") remaining_leaves = fields.Float( compute='_compute_remaining_leaves', string='Remaining Paid Time Off', help='Total number of paid time off allocated to this employee, change this value to create allocation/time off request. ' 'Total based on all the time off types without overriding limit.') current_leave_state = fields.Selection(compute='_compute_leave_status', string="Current Time Off Status", selection=[ ('draft', 'New'), ('confirm', 'Waiting Approval'), ('refuse', 'Refused'), ('validate1', 'Waiting Second Approval'), ('validate', 'Approved'), ('cancel', 'Cancelled') ]) current_leave_id = fields.Many2one('hr.leave.type', compute='_compute_leave_status', string="Current Time Off Type") leave_date_from = fields.Date('From Date', compute='_compute_leave_status') leave_date_to = fields.Date('To Date', compute='_compute_leave_status') leave_consolidated_date_to = fields.Date('Leave Consolidated To Date', compute='_compute_leave_consolidated_date_to') leaves_count = fields.Float('Number of Time Off', compute='_compute_remaining_leaves') allocation_count = fields.Float('Total number of days allocated.', compute='_compute_allocation_count') allocation_used_count = fields.Float('Total number of days off used', compute='_compute_total_allocation_used') show_leaves = fields.Boolean('Able to see Remaining Time Off', compute='_compute_show_leaves') is_absent = fields.Boolean('Absent Today', compute='_compute_leave_status', search='_search_absent_employee') allocation_display = fields.Char(compute='_compute_allocation_count') allocation_used_display = fields.Char(compute='_compute_total_allocation_used') def _get_date_start_work(self): return self.create_date def _get_remaining_leaves(self): """ Helper to compute the remaining leaves for the current employees :returns dict where the key is the employee id, and the value is the remain leaves """ self._cr.execute(""" SELECT sum(h.number_of_days) AS days, h.employee_id FROM ( SELECT holiday_status_id, number_of_days, state, employee_id FROM hr_leave_allocation UNION ALL SELECT holiday_status_id, (number_of_days * -1) as number_of_days, state, employee_id FROM hr_leave ) h join hr_leave_type s ON (s.id=h.holiday_status_id) WHERE s.active = true AND h.state='validate' AND (s.allocation_type='fixed' OR s.allocation_type='fixed_allocation') AND h.employee_id in %s GROUP BY h.employee_id""", (tuple(self.ids),)) return dict((row['employee_id'], row['days']) for row in self._cr.dictfetchall()) def _compute_remaining_leaves(self): remaining = self._get_remaining_leaves() for employee in self: value = float_round(remaining.get(employee.id, 0.0), precision_digits=2) employee.leaves_count = value employee.remaining_leaves = value def _compute_allocation_count(self): for employee in self: allocations = self.env['hr.leave.allocation'].search([ ('employee_id', '=', employee.id), ('holiday_status_id.active', '=', True), ('state', '=', 'validate'), '|', ('date_to', '=', False), ('date_to', '>=', datetime.date.today()), ]) employee.allocation_count = sum(allocations.mapped('number_of_days')) employee.allocation_display = "%g" % employee.allocation_count def _compute_total_allocation_used(self): for employee in self: employee.allocation_used_count = employee.allocation_count - employee.remaining_leaves employee.allocation_used_display = "%g" % employee.allocation_used_count def _compute_presence_state(self): super()._compute_presence_state() employees = self.filtered(lambda employee: employee.hr_presence_state != 'present' and employee.is_absent) employees.update({'hr_presence_state': 'absent'}) def _compute_leave_consolidated_date_to(self): holidays = self.env['hr.leave'].sudo().search([ ('employee_id', 'in', self.ids), ('date_to', '>=', fields.Datetime.now()), ('state', 'not in', ('cancel', 'refuse')) ], order="date_from") leave_data = defaultdict(list) for holiday in holidays: leave_data[holiday.employee_id].append({'date_to': holiday.date_to.date(), 'date_from': holiday.date_from.date()}) for employee, leave_data in leave_data.items(): first_leave = leave_data[0] date_to = first_leave['date_to'] working_days = employee.mapped('resource_calendar_id.attendance_ids.dayofweek') days = 1 for next_leave in leave_data: while str((first_leave['date_to'] + timedelta(days=days)).weekday()) not in working_days: days += 1 if next_leave['date_from'] == first_leave['date_to'] + timedelta(days=days): days = 1 date_to = next_leave['date_to'] first_leave = next_leave employee.leave_consolidated_date_to = date_to for employee in self - holidays.employee_id: employee.leave_consolidated_date_to = False def _compute_leave_status(self): # Used SUPERUSER_ID to forcefully get status of other user's leave, to bypass record rule holidays = self.env['hr.leave'].sudo().search([ ('employee_id', 'in', self.ids), ('date_from', '<=', fields.Datetime.now()), ('date_to', '>=', fields.Datetime.now()), ('state', 'not in', ('cancel', 'refuse')) ]) leave_data = {} for holiday in holidays: leave_data[holiday.employee_id.id] = {} leave_data[holiday.employee_id.id]['leave_date_from'] = holiday.date_from.date() leave_data[holiday.employee_id.id]['leave_date_to'] = holiday.date_to.date() leave_data[holiday.employee_id.id]['current_leave_state'] = holiday.state leave_data[holiday.employee_id.id]['current_leave_id'] = holiday.holiday_status_id.id for employee in self: employee.leave_date_from = leave_data.get(employee.id, {}).get('leave_date_from') employee.leave_date_to = leave_data.get(employee.id, {}).get('leave_date_to') employee.current_leave_state = leave_data.get(employee.id, {}).get('current_leave_state') employee.current_leave_id = leave_data.get(employee.id, {}).get('current_leave_id') employee.is_absent = leave_data.get(employee.id) and leave_data.get(employee.id, {}).get('current_leave_state') not in ['cancel', 'refuse', 'draft'] @api.depends('parent_id') def _compute_leave_manager(self): for employee in self: previous_manager = employee._origin.parent_id.user_id manager = employee.parent_id.user_id if manager and employee.leave_manager_id == previous_manager or not employee.leave_manager_id: employee.leave_manager_id = manager def _compute_show_leaves(self): show_leaves = self.env['res.users'].has_group('hr_holidays.group_hr_holidays_user') for employee in self: if show_leaves or employee.user_id == self.env.user: employee.show_leaves = True else: employee.show_leaves = False def _search_absent_employee(self, operator, value): holidays = self.env['hr.leave'].sudo().search([ ('employee_id', '!=', False), ('state', 'not in', ['cancel', 'refuse']), ('date_from', '<=', datetime.datetime.utcnow()), ('date_to', '>=', datetime.datetime.utcnow()) ]) return [('id', 'in', holidays.mapped('employee_id').ids)] def write(self, values): res = super(HrEmployeeBase, self).write(values) if 'parent_id' in values or 'department_id' in values: today_date = fields.Datetime.now() hr_vals = {} if values.get('parent_id') is not None: hr_vals['manager_id'] = values['parent_id'] if values.get('department_id') is not None: hr_vals['department_id'] = values['department_id'] holidays = self.env['hr.leave'].sudo().search(['|', ('state', 'in', ['draft', 'confirm']), ('date_from', '>', today_date), ('employee_id', 'in', self.ids)]) holidays.write(hr_vals) allocations = self.env['hr.leave.allocation'].sudo().search([('state', 'in', ['draft', 'confirm']), ('employee_id', 'in', self.ids)]) allocations.write(hr_vals) return res
class DeliveryCarrier(models.Model): _name = 'delivery.carrier' _description = "Delivery Methods" _order = 'sequence, id' ''' A Shipping Provider In order to add your own external provider, follow these steps: 1. Create your model MyProvider that _inherit 'delivery.carrier' 2. Extend the selection of the field "delivery_type" with a pair ('<my_provider>', 'My Provider') 3. Add your methods: <my_provider>_rate_shipment <my_provider>_send_shipping <my_provider>_get_tracking_link <my_provider>_cancel_shipment _<my_provider>_get_default_custom_package_code (they are documented hereunder) ''' # -------------------------------- # # Internals for shipping providers # # -------------------------------- # name = fields.Char('Delivery Method', required=True, translate=True) active = fields.Boolean(default=True) sequence = fields.Integer(help="Determine the display order", default=10) # This field will be overwritten by internal shipping providers by adding their own type (ex: 'fedex') delivery_type = fields.Selection([('fixed', 'Fixed Price')], string='Provider', default='fixed', required=True) integration_level = fields.Selection( [('rate', 'Get Rate'), ('rate_and_ship', 'Get Rate and Create Shipment')], string="Integration Level", default='rate_and_ship', help="Action while validating Delivery Orders") prod_environment = fields.Boolean( "Environment", help="Set to True if your credentials are certified for production.") debug_logging = fields.Boolean( 'Debug logging', help="Log requests in order to ease debugging") company_id = fields.Many2one('res.company', string='Company', related='product_id.company_id', store=True, readonly=False) product_id = fields.Many2one('product.product', string='Delivery Product', required=True, ondelete='restrict') invoice_policy = fields.Selection( [('estimated', 'Estimated cost'), ('real', 'Real cost')], string='Invoicing Policy', default='estimated', required=True, help= "Estimated Cost: the customer will be invoiced the estimated cost of the shipping.\nReal Cost: the customer will be invoiced the real cost of the shipping, the cost of the shipping will be updated on the SO after the delivery." ) country_ids = fields.Many2many('res.country', 'delivery_carrier_country_rel', 'carrier_id', 'country_id', 'Countries') state_ids = fields.Many2many('res.country.state', 'delivery_carrier_state_rel', 'carrier_id', 'state_id', 'States') zip_from = fields.Char('Zip From') zip_to = fields.Char('Zip To') margin = fields.Integer( help='This percentage will be added to the shipping price.') free_over = fields.Boolean( 'Free if order amount is above', help= "If the order total amount (shipping excluded) is above or equal to this value, the customer benefits from a free shipping", default=False, oldname='free_if_more_than') amount = fields.Float( string='Amount', help= "Amount of the order to benefit from a free shipping, expressed in the company currency" ) _sql_constraints = [ ('margin_not_under_100_percent', 'CHECK (margin >= -100)', 'Margin cannot be lower than -100%'), ] def toggle_prod_environment(self): for c in self: c.prod_environment = not c.prod_environment def toggle_debug(self): for c in self: c.debug_logging = not c.debug_logging @api.multi def install_more_provider(self): return { 'name': 'New Providers', 'view_mode': 'kanban,form', 'res_model': 'ir.module.module', 'domain': [['name', 'ilike', 'delivery_']], 'type': 'ir.actions.act_window', 'help': _('''<p class="o_view_nocontent"> Buy Odoo Enterprise now to get more providers. </p>'''), } def available_carriers(self, partner): return self.filtered(lambda c: c._match_address(partner)) def _match_address(self, partner): self.ensure_one() if self.country_ids and partner.country_id not in self.country_ids: return False if self.state_ids and partner.state_id not in self.state_ids: return False if self.zip_from and (partner.zip or '').upper() < self.zip_from.upper(): return False if self.zip_to and (partner.zip or '').upper() > self.zip_to.upper(): return False return True @api.onchange('state_ids') def onchange_states(self): self.country_ids = [ (6, 0, self.country_ids.ids + self.state_ids.mapped('country_id.id')) ] @api.onchange('country_ids') def onchange_countries(self): self.state_ids = [ (6, 0, self.state_ids.filtered(lambda state: state.id in self.country_ids .mapped('state_ids').ids).ids) ] # -------------------------- # # API for external providers # # -------------------------- # def rate_shipment(self, order): ''' Compute the price of the order shipment :param order: record of sale.order :return dict: {'success': boolean, 'price': a float, 'error_message': a string containing an error message, 'warning_message': a string containing a warning message} # TODO maybe the currency code? ''' self.ensure_one() if hasattr(self, '%s_rate_shipment' % self.delivery_type): res = getattr(self, '%s_rate_shipment' % self.delivery_type)(order) # apply margin on computed price res['price'] = float(res['price']) * (1.0 + (float(self.margin) / 100.0)) # free when order is large enough if res['success'] and self.free_over and order._compute_amount_total_without_delivery( ) >= self.amount: res['warning_message'] = _( 'Info:\nThe shipping is free because the order amount exceeds %.2f.\n(The actual shipping cost is: %.2f)' ) % (self.amount, res['price']) res['price'] = 0.0 return res def send_shipping(self, pickings): ''' Send the package to the service provider :param pickings: A recordset of pickings :return list: A list of dictionaries (one per picking) containing of the form:: { 'exact_price': price, 'tracking_number': number } # TODO missing labels per package # TODO missing currency # TODO missing success, error, warnings ''' self.ensure_one() if hasattr(self, '%s_send_shipping' % self.delivery_type): return getattr(self, '%s_send_shipping' % self.delivery_type)(pickings) def get_tracking_link(self, picking): ''' Ask the tracking link to the service provider :param picking: record of stock.picking :return str: an URL containing the tracking link or False ''' self.ensure_one() if hasattr(self, '%s_get_tracking_link' % self.delivery_type): return getattr(self, '%s_get_tracking_link' % self.delivery_type)(picking) def cancel_shipment(self, pickings): ''' Cancel a shipment :param pickings: A recordset of pickings ''' self.ensure_one() if hasattr(self, '%s_cancel_shipment' % self.delivery_type): return getattr(self, '%s_cancel_shipment' % self.delivery_type)(pickings) def log_xml(self, xml_string, func): self.ensure_one() if self.debug_logging: db_name = self._cr.dbname # Use a new cursor to avoid rollback that could be caused by an upper method try: db_registry = registry(db_name) with db_registry.cursor() as cr: env = api.Environment(cr, SUPERUSER_ID, {}) IrLogging = env['ir.logging'] IrLogging.sudo().create({ 'name': 'delivery.carrier', 'type': 'server', 'dbname': db_name, 'level': 'DEBUG', 'message': xml_string, 'path': self.delivery_type, 'func': func, 'line': 1 }) except psycopg2.Error: pass def _get_default_custom_package_code(self): """ Some delivery carriers require a prefix to be sent in order to use custom packages (ie not official ones). This optional method will return it as a string. """ self.ensure_one() if hasattr(self, '_%s_get_default_custom_package_code' % self.delivery_type): return getattr( self, '_%s_get_default_custom_package_code' % self.delivery_type)() else: return False # ------------------------------------------------ # # Fixed price shipping, aka a very simple provider # # ------------------------------------------------ # fixed_price = fields.Float(compute='_compute_fixed_price', inverse='_set_product_fixed_price', store=True, string='Fixed Price') @api.depends('product_id.list_price', 'product_id.product_tmpl_id.list_price') def _compute_fixed_price(self): for carrier in self: carrier.fixed_price = carrier.product_id.list_price def _set_product_fixed_price(self): for carrier in self: carrier.product_id.list_price = carrier.fixed_price def fixed_rate_shipment(self, order): carrier = self._match_address(order.partner_shipping_id) if not carrier: return { 'success': False, 'price': 0.0, 'error_message': _('Error: this delivery method is not available for this address.' ), 'warning_message': False } price = self.fixed_price if self.company_id and self.company_id.currency_id.id != order.currency_id.id: price = self.env['res.currency']._compute( self.company_id.currency_id, order.currency_id, price) return { 'success': True, 'price': price, 'error_message': False, 'warning_message': False } def fixed_send_shipping(self, pickings): res = [] for p in pickings: res = res + [{ 'exact_price': p.carrier_id.fixed_price, 'tracking_number': False }] return res def fixed_get_tracking_link(self, picking): return False def fixed_cancel_shipment(self, pickings): raise NotImplementedError()
class BuyAdjustLine(models.Model): _name = 'buy.adjust.line' _description = u'采购变更单明细' @api.one @api.depends('goods_id') def _compute_using_attribute(self): '''返回订单行中商品是否使用属性''' self.using_attribute = self.goods_id.attribute_ids and True or False @api.one @api.depends('quantity', 'price_taxed', 'discount_amount', 'tax_rate') def _compute_all_amount(self): '''当订单行的数量、单价、折扣额、税率改变时,改变购货金额、税额、价税合计''' if self.tax_rate > 100: raise UserError(u'税率不能输入超过100的数\n税率:%s!' % self.tax_rate) if self.tax_rate < 0: raise UserError(u'税率不能输入负数\n税率:%s!' % self.tax_rate) self.subtotal = self.price_taxed * self.quantity - self.discount_amount # 价税合计 self.tax_amount = self.subtotal / \ (100 + self.tax_rate) * self.tax_rate # 税额 self.amount = self.subtotal - self.tax_amount # 金额 @api.onchange('price', 'tax_rate') def onchange_price(self): '''当订单行的不含税单价改变时,改变含税单价''' price = self.price_taxed / (1 + self.tax_rate * 0.01) # 不含税单价 decimal = self.env.ref('core.decimal_price') if float_compare(price, self.price, precision_digits=decimal.digits) != 0: self.price_taxed = self.price * (1 + self.tax_rate * 0.01) order_id = fields.Many2one('buy.adjust', u'订单编号', index=True, required=True, ondelete='cascade', help=u'关联的变更单编号') goods_id = fields.Many2one('goods', u'商品', ondelete='restrict', help=u'商品') using_attribute = fields.Boolean(u'使用属性', compute=_compute_using_attribute, help=u'商品是否使用属性') attribute_id = fields.Many2one('attribute', u'属性', ondelete='restrict', domain="[('goods_id', '=', goods_id)]", help=u'商品的属性,当商品有属性时,该字段必输') uom_id = fields.Many2one('uom', u'单位', ondelete='restrict', help=u'商品计量单位') quantity = fields.Float(u'调整数量', default=1, required=True, digits=dp.get_precision('Quantity'), help=u'相对于原单据对应明细行的调整数量,可正可负') price = fields.Float(u'购货单价', store=True, digits=dp.get_precision('Price'), help=u'不含税单价,由含税单价计算得出') price_taxed = fields.Float(u'含税单价', digits=dp.get_precision('Price'), help=u'含税单价,取自商品成本') discount_rate = fields.Float(u'折扣率%', help=u'折扣率') discount_amount = fields.Float(u'折扣额', digits=dp.get_precision('Amount'), help=u'输入折扣率后自动计算得出,也可手动输入折扣额') amount = fields.Float(u'金额', compute=_compute_all_amount, store=True, readonly=True, digits=dp.get_precision('Amount'), help=u'金额 = 价税合计 - 税额') tax_rate = fields.Float( u'税率(%)', default=lambda self: self.env.user.company_id.import_tax_rate, help=u'默认值取公司进项税率') tax_amount = fields.Float(u'税额', compute=_compute_all_amount, store=True, readonly=True, digits=dp.get_precision('Amount'), help=u'由税率计算得出') subtotal = fields.Float(u'价税合计', compute=_compute_all_amount, store=True, readonly=True, digits=dp.get_precision('Amount'), help=u'含税单价 乘以 数量') note = fields.Char(u'备注', help=u'本行备注') company_id = fields.Many2one( 'res.company', string=u'公司', change_default=True, default=lambda self: self.env['res.company']._company_default_get()) @api.onchange('goods_id') def onchange_goods_id(self): '''当订单行的商品变化时,带出商品上的单位、默认仓库、成本价''' if self.goods_id: self.uom_id = self.goods_id.uom_id self.price_taxed = self.goods_id.cost self.tax_rate = self.goods_id.get_tax_rate( self.goods_id, self.order_id.order_id.partner_id, 'buy') @api.onchange('quantity', 'price_taxed', 'discount_rate') def onchange_discount_rate(self): '''当数量、单价或优惠率发生变化时,优惠金额发生变化''' self.price = self.price_taxed / (1 + self.tax_rate * 0.01) self.discount_amount = (self.quantity * self.price * self.discount_rate * 0.01) @api.one @api.constrains('attribute_id') def check_attribute(self): '''检查属性是否填充,防止无权限人员不填就可以保存''' if self.using_attribute and not self.attribute_id: raise UserError(u'请输入商品:%s 的属性' % self.goods_id.name)
class WorkshopVehicleCost(models.Model): _inherit = ['mail.thread', 'mail.activity.mixin'] _name = 'workshop.vehicle.cost' _description = 'Cost related to a vehicle' _order = 'date desc, vehicle_id asc' name = fields.Char(related='vehicle_id.name', string='Name', store=True, readonly=False) owner_id = fields.Many2one('res.partner', related='vehicle_id.driver_id', string='Owner', store=True, readonly=True) vehicle_id = fields.Many2one('workshop.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this log') cost_subtype_id = fields.Many2one( 'workshop.service.type', 'Type', help='Cost type purchased with this cost') amount = fields.Monetary('Total Price') cost_type = fields.Selection([('contract', 'Contract'), ('services', 'Services'), ('fuel', 'Fuel'), ('other', 'Other')], 'Category of the cost', default="other", help='For internal purpose only', required=True) operation_type = fields.Selection([('action_taken', 'Action Taken'), ('complaint', 'Customers Complaint'), ('diagnosis', 'Diagnostic Finding')]) parent_id = fields.Many2one('workshop.vehicle.cost', 'Parent', help='Parent cost to this current cost') cost_ids = fields.One2many('workshop.vehicle.cost', 'parent_id', 'Included Services', copy=True) odometer_id = fields.Many2one( 'workshop.vehicle.odometer', 'Odometer', help='Odometer measure of the vehicle at the moment of this log') odometer = fields.Float( compute="_get_odometer", inverse='_set_odometer', string='Odometer Value', help='Odometer measure of the vehicle at the moment of this log') odometer_unit = fields.Selection(related='vehicle_id.odometer_unit', string="Unit", readonly=True) date = fields.Date(help='Date when the cost has been executed') # contract_id = fields.Many2one('fleet.vehicle.log.contract', 'Contract', help='Contract attached to this cost') auto_generated = fields.Boolean('Automatically Generated', readonly=True) description = fields.Char("Cost Description") company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company) currency_id = fields.Many2one('res.currency', related='company_id.currency_id') def _get_odometer(self): self.odometer = 0.0 for record in self: record.odometer = False if record.odometer_id: record.odometer = record.odometer_id.value def _set_odometer(self): for record in self: if not record.odometer: raise UserError( _('Emptying the odometer value of a vehicle is not allowed.' )) odometer = self.env['workshop.vehicle.odometer'].create({ 'value': record.odometer, 'date': record.date or fields.Date.context_today(record), 'vehicle_id': record.vehicle_id.id }) self.odometer_id = odometer @api.model_create_multi def create(self, vals_list): for data in vals_list: # make sure that the data are consistent with values of parent and contract records given if 'parent_id' in data and data['parent_id']: parent = self.browse(data['parent_id']) data['vehicle_id'] = parent.vehicle_id.id data['date'] = parent.date data['cost_type'] = parent.cost_type # if 'contract_id' in data and data['contract_id']: # contract = self.env['workshop.vehicle.log.contract'].browse(data['contract_id']) # data['vehicle_id'] = contract.vehicle_id.id # data['cost_subtype_id'] = contract.cost_subtype_id.id # data['cost_type'] = contract.cost_type if 'odometer' in data and not data['odometer']: # if received value for odometer is 0, then remove it from the # data as it would result to the creation of a # odometer log with 0, which is to be avoided del data['odometer'] return super(WorkshopVehicleCost, self).create(vals_list)
class BuyAdjust(models.Model): _name = "buy.adjust" _inherit = ['mail.thread'] _description = u"采购变更单" _order = 'date desc, id desc' name = fields.Char(u'单据编号', copy=False, help=u'变更单编号,保存时可自动生成') order_id = fields.Many2one('buy.order', u'原始单据', states=READONLY_STATES, copy=False, ondelete='restrict', help=u'要调整的原始购货订单,只能调整已确认且没有全部入库的购货订单') date = fields.Date(u'单据日期', states=READONLY_STATES, default=lambda self: fields.Date.context_today(self), index=True, copy=False, help=u'变更单创建日期,默认是当前日期') line_ids = fields.One2many('buy.adjust.line', 'order_id', u'变更单行', states=READONLY_STATES, copy=True, help=u'变更单明细行,不允许为空') approve_uid = fields.Many2one('res.users', u'确认人', copy=False, ondelete='restrict', help=u'确认变更单的人') state = fields.Selection(BUY_ORDER_STATES, u'确认状态', index=True, copy=False, default='draft', help=u'变更单确认状态') note = fields.Text(u'备注', help=u'单据备注') user_id = fields.Many2one( 'res.users', u'经办人', ondelete='restrict', states=READONLY_STATES, default=lambda self: self.env.user, help=u'单据经办人', ) company_id = fields.Many2one( 'res.company', string=u'公司', change_default=True, default=lambda self: self.env['res.company']._company_default_get()) def _get_vals(self, line): '''返回创建 buy order line 时所需数据''' return { 'order_id': self.order_id.id, 'goods_id': line.goods_id.id, 'attribute_id': line.attribute_id.id, 'quantity': line.quantity, 'uom_id': line.uom_id.id, 'price_taxed': line.price_taxed, 'discount_rate': line.discount_rate, 'discount_amount': line.discount_amount, 'tax_rate': line.tax_rate, 'note': line.note or '', } @api.one def buy_adjust_done(self): '''确认采购变更单: 当调整后数量 < 原单据中已入库数量,则报错; 当调整后数量 > 原单据中已入库数量,则更新原单据及入库单分单的数量; 当调整后数量 = 原单据中已入库数量,则更新原单据数量,删除入库单分单; 当新增商品时,则更新原单据及入库单分单明细行。 ''' if self.state == 'done': raise UserError(u'请不要重复确认!\n采购变更单%s已确认' % self.name) if not self.line_ids: raise UserError(u'请输入商品明细行!') for line in self.line_ids: if line.price_taxed < 0: raise UserError(u'商品含税单价不能小于0!\n单价:%s' % line.price_taxed) buy_receipt = self.env['buy.receipt'].search([('order_id', '=', self.order_id.id), ('state', '=', 'draft')]) if not buy_receipt: raise UserError(u'采购入库单已全部入库,不能调整') for line in self.line_ids: origin_line = self.env['buy.order.line'].search([ ('goods_id', '=', line.goods_id.id), ('attribute_id', '=', line.attribute_id.id), ('order_id', '=', self.order_id.id) ]) if len(origin_line) > 1: raise UserError(u'要调整的商品%s在原始单据中不唯一' % line.goods_id.name) if origin_line: origin_line.quantity += line.quantity # 调整后数量 new_note = u'变更单:%s %s。\n' % (self.name, line.note) origin_line.note = (origin_line.note and origin_line.note + new_note or new_note) if origin_line.quantity < origin_line.quantity_in: raise UserError(u'%s调整后数量不能小于原订单已入库数量' % line.goods_id.name) elif origin_line.quantity > origin_line.quantity_in: # 查找出原购货订单产生的草稿状态的入库单明细行,并更新它 move_line = self.env['wh.move.line'].search([ ('buy_line_id', '=', origin_line.id), ('state', '=', 'draft') ]) if move_line: move_line.goods_qty += line.quantity move_line.note = (move_line.note and move_line.note or move_line.note + origin_line.note) else: raise UserError(u'商品%s已全部入库,建议新建购货订单' % line.goods_id.name) # 调整后数量与已入库数量相等时,删除产生的入库单分单 else: buy_receipt.unlink() else: new_line = self.env['buy.order.line'].create( self._get_vals(line)) receipt_line = [] if line.goods_id.force_batch_one: i = 0 while i < line.quantity: i += 1 receipt_line.append( self.order_id.get_receipt_line(new_line, single=True)) else: receipt_line.append( self.order_id.get_receipt_line(new_line, single=False)) buy_receipt.write( {'line_in_ids': [(0, 0, li[0]) for li in receipt_line]}) self.state = 'done' self.approve_uid = self._uid
class AcademyTestsTest(models.Model): """ Stored tests which can be reused in future """ _name = 'academy.tests.test' _description = u'Academy tests, test' _rec_name = 'name' _order = 'write_date DESC, create_date DESC' _inherit = ['image.mixin', 'mail.thread'] name = fields.Char(string='Name', required=True, readonly=False, index=True, default=None, help="Name for this test", size=255, translate=True, track_visibility='onchange') description = fields.Text(string='Description', required=False, readonly=False, index=False, default=None, help='Something about this test', translate=True) active = fields.Boolean( string='Active', required=False, readonly=False, index=False, default=True, help=('If the active field is set to false, it will allow you to ' 'hide record without removing it')) code = fields.Char(string='Code', required=False, readonly=False, index=True, default=None, help='Internal code', size=20, translate=False) preamble = fields.Text(string='Preamble', required=False, readonly=False, index=False, default=None, help='What it is said before beginning to test', translate=True) question_ids = fields.One2many( string='Questions', required=False, readonly=False, index=False, default=None, help=False, comodel_name='academy.tests.test.question.rel', inverse_name='test_id', domain=[], context={}, auto_join=False, limit=None, # oldname='academy_question_ids' ) answers_table_ids = fields.One2many( string='Answers table', required=False, readonly=True, index=False, default=None, help='Summary with answers table', comodel_name='academy.tests.answers.table', inverse_name='test_id', domain=[], context={}, auto_join=False, limit=None, # oldname='academy_answers_table_ids' ) random_template_id = fields.Many2one( string='Template', required=False, readonly=True, index=False, default=None, help='Template has been used to pupulate this tests', comodel_name='academy.tests.random.template', domain=[], context={}, ondelete='cascade', auto_join=False) owner_id = fields.Many2one( string='Owner', required=True, readonly=False, index=False, default=lambda self: self._default_owner_id(), help='Current test owner', comodel_name='res.users', domain=[], context={}, ondelete='cascade', auto_join=False, ) test_kind_id = fields.Many2one(string='Kind of test', required=False, readonly=False, index=False, default=None, help='Choose the kind for this test', comodel_name='academy.tests.test.kind', domain=[], context={}, ondelete='cascade', auto_join=False) first_use_id = fields.Many2one(string='First use', required=False, readonly=False, index=False, default=None, help=False, comodel_name='res.partner', domain=[], context={}, ondelete='cascade', auto_join=False) training_action_ids = fields.Many2many( string='Training actions', required=False, readonly=False, index=False, default=None, help='Choose the training actions in which this test will be available', comodel_name='academy.training.action', relation='academy_tests_test_training_action_rel', column1='test_id', column2='training_action_id', domain=[], context={}, limit=None) training_activity_ids = fields.Many2many( string='Training activities', required=False, readonly=False, index=False, default=None, help= 'Choose the training activities in which this test will be available', comodel_name='academy.training.activity', relation='academy_tests_test_training_activity_rel', column1='test_id', column2='training_activity_id', domain=[], context={}, limit=None) training_module_ids = fields.Many2many( string='Training modules', required=False, readonly=False, index=False, default=None, help='Choose the training modules in which this test will be available', comodel_name='academy.training.module', relation='academy_tests_test_training_module_rel', column1='test_id', column2='training_module_id', domain=[('training_module_id', '=', False)], context={}, limit=None) competency_unit_ids = fields.Many2many( string='Competency units', required=False, readonly=False, index=False, default=None, help='Choose the competency units in which this test will be available', comodel_name='academy.competency.unit', relation='academy_tests_test_competency_unit_rel', column1='test_id', column2='competency_unit_id', domain=[], context={}, limit=None) lesson_ids = fields.Many2many( string='Lessons', required=False, readonly=False, index=False, default=None, help='Choose the lessons in which this test will be available', comodel_name='academy.training.lesson', relation='academy_tests_test_training_lesson_rel', column1='test_id', column2='lesson_id', domain=[], context={}, limit=None) time_by = fields.Selection(string='Time by', required=False, readonly=False, index=False, default=False, help=False, selection=[('test', 'Test'), ('question', 'Question')]) available_time = fields.Float( string='Time', required=False, readonly=False, index=False, default=0.0, digits=(16, 2), help='Available time to complete the exercise') lock_time = fields.Boolean( string='Lock time', required=False, readonly=False, index=False, default=True, help= 'Check to not allow the user to continue with the test once the time has passed' ) correction_scale_id = fields.Many2one( string='Correction scale', required=False, readonly=False, index=False, default=None, help='Choose the scale of correction', comodel_name='academy.tests.correction.scale', domain=[], context={}, ondelete='cascade', auto_join=False) available_in = fields.One2many( string='Available in', required=False, readonly=True, index=False, default=None, help='This test is directly related to', comodel_name='academy.tests.test.availability', inverse_name='test_id', domain=[], context={}, auto_join=False, limit=None) # -------------------------- MANAGEMENT FIELDS ---------------------------- question_count = fields.Integer(string='Number of questions', required=False, readonly=False, index=False, default=0, help='Number of questions in test', compute='_compute_question_count') @api.depends('question_ids') def _compute_question_count(self): for record in self: record.question_count = len(record.question_ids) topic_ids = Many2manyThroughView(string='Topics', required=False, readonly=True, index=False, default=None, help=False, comodel_name='academy.tests.topic', relation='academy_tests_test_topic_rel', column1='test_id', column2='topic_id', domain=[], context={}, limit=None, sql=ACADEMY_TESTS_TEST_TOPIC_IDS_SQL) topic_count = fields.Integer( string='Number of topics', required=False, readonly=True, index=False, default=0, help='Display the number of topics related with test', compute=lambda self: self._compute_topic_count()) @api.depends('question_ids') def _compute_topic_count(self): for record in self: question_set = record.question_ids.mapped('question_id') topic_set = question_set.mapped('topic_id') ids = topic_set.mapped('id') record.topic_count = len(ids) topic_id = fields.Many2one(string='Topic', required=False, readonly=True, index=False, default=None, help=False, comodel_name='academy.tests.topic', domain=[], context={}, ondelete='cascade', auto_join=False, compute=lambda self: self._compute_topic_id()) @api.depends('question_ids') def _compute_topic_id(self): for record in self: rel_ids = record.question_ids.filtered( lambda rel: rel.question_id.topic_id) question_ids = rel_ids.mapped('question_id') topics = {k.id: 0 for k in question_ids.mapped('topic_id')} if not topics: record.topic_id = None else: for question_id in question_ids: _id = question_id.topic_id.id topics[_id] = topics[_id] + 1 topic_id = max(topics.items(), key=itemgetter(1))[0] topic_obj = self.env['academy.tests.topic'] record.topic_id = topic_obj.browse(topic_id) available_in_enrolment_ids = Many2manyThroughView( string='Tests', required=False, readonly=False, index=False, default=None, help='Choose the enrolments in which this test will be available', comodel_name='academy.training.action.enrolment', relation= 'academy_tests_test_available_in_training_action_enrolment_rel', column1='test_id', column2='enrolment_id', domain=[], context={}, limit=None, sql=ACADEMY_ENROLMENT_AVAILABLE_TESTS) lang = fields.Char( string='Language', required=True, readonly=True, index=False, help=False, size=255, translate=False, compute='_compute_lang', ) # ----------------------- AUXILIARY FIELD METHODS ------------------------- def _default_owner_id(self): uid = 1 if 'uid' in self.env.context: uid = self.env.context['uid'] return uid @api.depends('name') def _compute_lang(self): """ Gets the language used by the current user and sets it as `lang` field value """ user_id = self.env['res.users'].browse(self.env.uid) for record in self: record.lang = user_id.lang def import_questions(self): """ Runs a wizard to import questions from plain text @note: actually this method is not used """ return { 'type': 'ir.actions.act_window', 'res_model': 'academy.tests.question.import.wizard', 'view_mode': 'form', 'views': [(False, 'form')], 'target': 'new', 'context': { 'default_test_id': self.id } } def random_questions(self): """ Runs wizard to append random questions. This allows uses to set filter criteria, maximum number of questions, etc. """ return { 'type': 'ir.actions.act_window', 'res_model': 'academy.tests.random.wizard', 'view_mode': 'form', 'views': [(False, 'form')], 'target': 'new', 'context': { 'default_test_id': self.id } } def show_questions(self): """ Runs default view for academy.tests.question with a filter to show only current test questions """ act_wnd = self.env.ref( 'academy_tests.action_questions_keep_items_act_window') if act_wnd.domain: if isinstance(act_wnd.domain, str): domain = safe_eval(act_wnd.domain) else: domain = act_wnd.domain else: domain = [] ids = self.question_ids.mapped('question_id').mapped('id') domain.append(('id', 'in', ids)) values = { 'type': act_wnd['type'], 'name': act_wnd['name'], 'res_model': act_wnd['res_model'], 'view_mode': act_wnd['view_mode'], 'target': act_wnd['target'], 'domain': domain, 'context': self.env.context, 'limit': act_wnd['limit'], 'help': act_wnd['help'], 'view_ids': act_wnd['view_ids'], 'views': act_wnd['views'] } if act_wnd.search_view_id: values['search_view_id'] = act_wnd['search_view_id'].id return values @api.model def create(self, values): """ Create a new record for a model AcademyTestsTest @param values: provides a data for new record @return: returns a id of new record """ result = super(AcademyTestsTest, self).create(values) result.resequence() return result def write(self, values): """ Update all record(s) in recordset, with new value comes as {values} @param values: dict of new values to be set @return: True on success, False otherwise """ result = super(AcademyTestsTest, self).write(values) self.resequence() return result @api.returns('self', lambda value: value.id) def copy(self, default=None): self.ensure_one() # STEP 1: Ensure default is a dictionary if default is None: default = {} # STEP 2: Make new name adding ``(copy)`` if 'name' not in default: default['name'] = _("%s (copy)") % self.name # STEP 3: Create new links for all questions in the original test if self.question_ids: leafs = self.question_ids.mapped(self._mapped_question_ids) if (leafs): default['question_ids'] = leafs # STEP 4: Call parent method result = super(AcademyTestsTest, self).copy(default=default) return result @staticmethod def _mapped_question_ids(item): return (0, 0, { 'test_id': item.test_id.id, 'question_id': item.question_id.id, 'sequence': item.sequence, 'active': item.active }) def resequence(self): """ This updates the sequence of the questions into the test """ # order_by = 'sequence ASC, write_date ASC, create_date ASC, id ASC' # rel_domain = [('test_id', '=', self.id)] # rel_obj = self.env['academy.tests.test.question.rel'] # rel_set = rel_obj.search(rel_domain, order=order_by) for record in self: rel_set = record.question_ids.sorted() index = 1 for rel_item in rel_set: rel_item.write({'sequence': index}) index = index + 1 def shuffle(self): qpositions = list(range(0, len(self.question_ids))) sequence = 1 random_shuffle(qpositions) for qposition in qpositions: self.question_ids[qposition].sequence = sequence sequence += 1 def _creation_subtype(self): xid = 'academy_tests.academy_tests_test_created' return self.env.ref(xid) def _track_subtype(self, init_values): self.ensure_one() xid = 'academy_tests.academy_tests_test_written' return self.env.ref(xid) @api.returns('mail.message', lambda value: value.id) def message_post(self, *, body='', subject=None, message_type='notification', email_from=None, author_id=None, parent_id=False, subtype_id=False, subtype=None, partner_ids=None, channel_ids=None, attachments=None, attachment_ids=None, add_sign=True, record_name=False, **kwargs): for record in self: for enrolment in record.available_in_enrolment_ids: enrolment.message_post(body=body, subject=subject, message_type=message_type, email_from=email_from, author_id=author_id, parent_id=parent_id, subtype_id=subtype_id, subtype=subtype, partner_ids=partner_ids, channel_ids=channel_ids, attachments=attachments, attachment_ids=attachment_ids, add_sign=add_sign, record_name=record_name, **kwargs) return super(AcademyTestsTest, self).message_post(body=body, subject=subject, message_type=message_type, email_from=email_from, author_id=author_id, parent_id=parent_id, subtype_id=subtype_id, subtype=subtype, partner_ids=partner_ids, channel_ids=channel_ids, attachments=attachments, attachment_ids=attachment_ids, add_sign=add_sign, record_name=record_name, **kwargs)
class HolidaysAllocation(models.Model): """ Allocation Requests Access specifications: similar to leave requests """ _name = "hr.leave.allocation" _description = "Time Off Allocation" _order = "create_date desc" _inherit = ['mail.thread', 'mail.activity.mixin'] _mail_post_access = 'read' def _default_holiday_status_id(self): if self.user_has_groups('hr_holidays.group_hr_holidays_user'): domain = [('has_valid_allocation', '=', True), ('requires_allocation', '=', 'yes')] else: domain = [('has_valid_allocation', '=', True), ('requires_allocation', '=', 'yes'), ('employee_requests', '=', 'yes')] return self.env['hr.leave.type'].search(domain, limit=1) def _domain_holiday_status_id(self): if self.user_has_groups('hr_holidays.group_hr_holidays_user'): return [('requires_allocation', '=', 'yes')] return [('employee_requests', '=', 'yes')] name = fields.Char('Description', compute='_compute_description', inverse='_inverse_description', search='_search_description', compute_sudo=False) name_validity = fields.Char('Description with validity', compute='_compute_description_validity') active = fields.Boolean(default=True) private_name = fields.Char('Allocation Description', groups='hr_holidays.group_hr_holidays_user') state = fields.Selection([ ('draft', 'To Submit'), ('cancel', 'Cancelled'), ('confirm', 'To Approve'), ('refuse', 'Refused'), ('validate', 'Approved') ], string='Status', readonly=True, tracking=True, copy=False, default='draft', help="The status is set to 'To Submit', when an allocation request is created." + "\nThe status is 'To Approve', when an allocation request is confirmed by user." + "\nThe status is 'Refused', when an allocation request is refused by manager." + "\nThe status is 'Approved', when an allocation request is approved by manager.") date_from = fields.Date('Start Date', index=True, copy=False, default=fields.Date.context_today, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, tracking=True, required=True) date_to = fields.Date('End Date', copy=False, tracking=True, states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]}) holiday_status_id = fields.Many2one( "hr.leave.type", compute='_compute_holiday_status_id', store=True, string="Time Off Type", required=True, readonly=False, states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]}, domain=_domain_holiday_status_id, default=_default_holiday_status_id) employee_id = fields.Many2one( 'hr.employee', compute='_compute_from_employee_ids', store=True, string='Employee', index=True, readonly=False, ondelete="restrict", tracking=True, states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]}) employee_company_id = fields.Many2one(related='employee_id.company_id', readonly=True, store=True) active_employee = fields.Boolean('Active Employee', related='employee_id.active', readonly=True) manager_id = fields.Many2one('hr.employee', compute='_compute_manager_id', store=True, string='Manager') notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}) # duration number_of_days = fields.Float( 'Number of Days', compute='_compute_from_holiday_status_id', store=True, readonly=False, tracking=True, default=1, help='Duration in days. Reference field to use when necessary.') number_of_days_display = fields.Float( 'Duration (days)', compute='_compute_number_of_days_display', states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help="If Accrual Allocation: Number of days allocated in addition to the ones you will get via the accrual' system.") number_of_hours_display = fields.Float( 'Duration (hours)', compute='_compute_number_of_hours_display', help="If Accrual Allocation: Number of hours allocated in addition to the ones you will get via the accrual' system.") duration_display = fields.Char('Allocated (Days/Hours)', compute='_compute_duration_display', help="Field allowing to see the allocation duration in days or hours depending on the type_request_unit") # details parent_id = fields.Many2one('hr.leave.allocation', string='Parent') linked_request_ids = fields.One2many('hr.leave.allocation', 'parent_id', string='Linked Requests') approver_id = fields.Many2one( 'hr.employee', string='First Approval', readonly=True, copy=False, help='This area is automatically filled by the user who validates the allocation') validation_type = fields.Selection(string='Validation Type', related='holiday_status_id.allocation_validation_type', readonly=True) can_reset = fields.Boolean('Can reset', compute='_compute_can_reset') can_approve = fields.Boolean('Can Approve', compute='_compute_can_approve') type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True) # mode holiday_type = fields.Selection([ ('employee', 'By Employee'), ('company', 'By Company'), ('department', 'By Department'), ('category', 'By Employee Tag')], string='Allocation Mode', readonly=True, required=True, default='employee', states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help="Allow to create requests in batchs:\n- By Employee: for a specific employee" "\n- By Company: all employees of the specified company" "\n- By Department: all employees of the specified department" "\n- By Employee Tag: all employees of the specific employee group category") employee_ids = fields.Many2many( 'hr.employee', compute='_compute_from_holiday_type', store=True, string='Employees', readonly=False, states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]}) multi_employee = fields.Boolean( compute='_compute_from_employee_ids', store=True, help='Holds whether this allocation concerns more than 1 employee') mode_company_id = fields.Many2one( 'res.company', compute='_compute_from_holiday_type', store=True, string='Company Mode', readonly=False, states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]}) department_id = fields.Many2one( 'hr.department', compute='_compute_department_id', store=True, string='Department', states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}) category_id = fields.Many2one( 'hr.employee.category', compute='_compute_from_holiday_type', store=True, string='Employee Tag', readonly=False, states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]}) # accrual configuration lastcall = fields.Date("Date of the last accrual allocation", readonly=True, default=fields.Date.context_today) nextcall = fields.Date("Date of the next accrual allocation", default=False, readonly=True) allocation_type = fields.Selection( [ ('regular', 'Regular Allocation'), ('accrual', 'Accrual Allocation') ], string="Allocation Type", default="regular", required=True, readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}) is_officer = fields.Boolean(compute='_compute_is_officer') accrual_plan_id = fields.Many2one('hr.leave.accrual.plan', compute="_compute_from_holiday_status_id", store=True, readonly=False, domain="['|', ('time_off_type_id', '=', False), ('time_off_type_id', '=', holiday_status_id)]", tracking=True) max_leaves = fields.Float(compute='_compute_leaves') leaves_taken = fields.Float(compute='_compute_leaves') taken_leave_ids = fields.One2many('hr.leave', 'holiday_allocation_id', domain="[('state', 'in', ['confirm', 'validate1', 'validate'])]") _sql_constraints = [ ('type_value', "CHECK( (holiday_type='employee' AND (employee_id IS NOT NULL OR multi_employee IS TRUE)) or " "(holiday_type='category' AND category_id IS NOT NULL) or " "(holiday_type='department' AND department_id IS NOT NULL) or " "(holiday_type='company' AND mode_company_id IS NOT NULL))", "The employee, department, company or employee category of this request is missing. Please make sure that your user login is linked to an employee."), ('duration_check', "CHECK( ( number_of_days > 0 AND allocation_type='regular') or (allocation_type != 'regular'))", "The duration must be greater than 0."), ] # The compute does not get triggered without a depends on record creation # aka keep the 'useless' depends @api.depends_context('uid') @api.depends('allocation_type') def _compute_is_officer(self): self.is_officer = self.env.user.has_group("hr_holidays.group_hr_holidays_user") @api.depends_context('uid') def _compute_description(self): self.check_access_rights('read') self.check_access_rule('read') is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user') for allocation in self: if is_officer or allocation.employee_id.user_id == self.env.user or allocation.employee_id.leave_manager_id == self.env.user: allocation.name = allocation.sudo().private_name else: allocation.name = '*****' def _inverse_description(self): is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user') for allocation in self: if is_officer or allocation.employee_id.user_id == self.env.user or allocation.employee_id.leave_manager_id == self.env.user: allocation.sudo().private_name = allocation.name def _search_description(self, operator, value): is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user') domain = [('private_name', operator, value)] if not is_officer: domain = expression.AND([domain, [('employee_id.user_id', '=', self.env.user.id)]]) allocations = self.sudo().search(domain) return [('id', 'in', allocations.ids)] @api.depends('name', 'date_from', 'date_to') def _compute_description_validity(self): for allocation in self: if allocation.date_to: name_validity = _("%s (from %s to %s)", allocation.name, allocation.date_from.strftime("%b %d %Y"), allocation.date_to.strftime("%b %d %Y")) else: name_validity = _("%s (from %s to No Limit)", allocation.name, allocation.date_from.strftime("%b %d %Y")) allocation.name_validity = name_validity @api.depends('employee_id', 'holiday_status_id', 'taken_leave_ids.number_of_days', 'taken_leave_ids.state') def _compute_leaves(self): for allocation in self: allocation.max_leaves = allocation.number_of_hours_display if allocation.type_request_unit == 'hour' else allocation.number_of_days allocation.leaves_taken = sum(taken_leave.number_of_hours_display if taken_leave.leave_type_request_unit == 'hour' else taken_leave.number_of_days\ for taken_leave in allocation.taken_leave_ids\ if taken_leave.state == 'validate') @api.depends('number_of_days') def _compute_number_of_days_display(self): for allocation in self: allocation.number_of_days_display = allocation.number_of_days @api.depends('number_of_days', 'employee_id') def _compute_number_of_hours_display(self): for allocation in self: if allocation.parent_id and allocation.parent_id.type_request_unit == "hour": allocation.number_of_hours_display = allocation.number_of_days * HOURS_PER_DAY elif allocation.number_of_days: allocation.number_of_hours_display = allocation.number_of_days * (allocation.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY) else: allocation.number_of_hours_display = 0.0 @api.depends('number_of_hours_display', 'number_of_days_display') def _compute_duration_display(self): for allocation in self: allocation.duration_display = '%g %s' % ( (float_round(allocation.number_of_hours_display, precision_digits=2) if allocation.type_request_unit == 'hour' else float_round(allocation.number_of_days_display, precision_digits=2)), _('hours') if allocation.type_request_unit == 'hour' else _('days')) @api.depends('state', 'employee_id', 'department_id') def _compute_can_reset(self): for allocation in self: try: allocation._check_approval_update('draft') except (AccessError, UserError): allocation.can_reset = False else: allocation.can_reset = True @api.depends('state', 'employee_id', 'department_id') def _compute_can_approve(self): for allocation in self: try: if allocation.state == 'confirm' and allocation.validation_type != 'no': allocation._check_approval_update('validate') except (AccessError, UserError): allocation.can_approve = False else: allocation.can_approve = True @api.depends('employee_ids') def _compute_from_employee_ids(self): for allocation in self: if len(allocation.employee_ids) == 1: allocation.employee_id = allocation.employee_ids[0]._origin else: allocation.employee_id = False allocation.multi_employee = (len(allocation.employee_ids) > 1) @api.depends('holiday_type') def _compute_from_holiday_type(self): for allocation in self: if allocation.holiday_type == 'employee': if not allocation.employee_ids: allocation.employee_ids = self.env.user.employee_id allocation.mode_company_id = False allocation.category_id = False if allocation.holiday_type == 'company': allocation.employee_ids = False if not allocation.mode_company_id: allocation.mode_company_id = self.env.company allocation.category_id = False elif allocation.holiday_type == 'department': allocation.employee_ids = False allocation.mode_company_id = False allocation.category_id = False elif allocation.holiday_type == 'category': allocation.employee_ids = False allocation.mode_company_id = False else: allocation.employee_ids = self.env.context.get('default_employee_id') or self.env.user.employee_id @api.depends('holiday_type', 'employee_id') def _compute_department_id(self): for allocation in self: if allocation.holiday_type == 'employee': allocation.department_id = allocation.employee_id.department_id elif allocation.holiday_type == 'department': if not allocation.department_id: allocation.department_id = self.env.user.employee_id.department_id elif allocation.holiday_type == 'category': allocation.department_id = False @api.depends('employee_id') def _compute_manager_id(self): for allocation in self: allocation.manager_id = allocation.employee_id and allocation.employee_id.parent_id @api.depends('accrual_plan_id') def _compute_holiday_status_id(self): default_holiday_status_id = None for holiday in self: if not holiday.holiday_status_id: if holiday.accrual_plan_id: holiday.holiday_status_id = holiday.accrual_plan_id.time_off_type_id else: if not default_holiday_status_id: # fetch when we need it default_holiday_status_id = self._default_holiday_status_id() holiday.holiday_status_id = default_holiday_status_id @api.depends('holiday_status_id', 'allocation_type', 'number_of_hours_display', 'number_of_days_display', 'date_to') def _compute_from_holiday_status_id(self): accrual_allocations = self.filtered(lambda alloc: alloc.allocation_type == 'accrual' and not alloc.accrual_plan_id and alloc.holiday_status_id) accruals_dict = {} if accrual_allocations: accruals_read_group = self.env['hr.leave.accrual.plan'].read_group( [('time_off_type_id', 'in', accrual_allocations.holiday_status_id.ids)], ['time_off_type_id', 'ids:array_agg(id)'], ['time_off_type_id'], ) accruals_dict = {res['time_off_type_id'][0]: res['ids'] for res in accruals_read_group} for allocation in self: allocation.number_of_days = allocation.number_of_days_display if allocation.type_request_unit == 'hour': allocation.number_of_days = allocation.number_of_hours_display / (allocation.employee_id.sudo().resource_calendar_id.hours_per_day or HOURS_PER_DAY) if allocation.accrual_plan_id.time_off_type_id.id not in (False, allocation.holiday_status_id.id): allocation.accrual_plan_id = False if allocation.allocation_type == 'accrual' and not allocation.accrual_plan_id: if allocation.holiday_status_id: allocation.accrual_plan_id = accruals_dict.get(allocation.holiday_status_id.id, [False])[0] def _end_of_year_accrual(self): # to override in payroll today = fields.Date.today() for allocation in self: current_level = allocation._get_current_accrual_plan_level_id(today)[0] if current_level and current_level.action_with_unused_accruals == 'lost': # Allocations are lost but number_of_days should not be lower than leaves_taken allocation.write({'number_of_days': allocation.leaves_taken, 'lastcall': today, 'nextcall': False}) def _get_current_accrual_plan_level_id(self, date, level_ids=False): """ Returns a pair (accrual_plan_level, idx) where accrual_plan_level is the level for the given date and idx is the index for the plan in the ordered set of levels """ self.ensure_one() if not self.accrual_plan_id.level_ids: return (False, False) # Sort by sequence which should be equivalent to the level if not level_ids: level_ids = self.accrual_plan_id.level_ids.sorted('sequence') current_level = False current_level_idx = -1 for idx, level in enumerate(level_ids): if date >= self.date_from + get_timedelta(level.start_count, level.start_type): current_level = level current_level_idx = idx # If transition_mode is set to `immediately` or we are currently on the first level # the current_level is simply the first level in the list. if current_level_idx <= 0 or self.accrual_plan_id.transition_mode == "immediately": return (current_level, current_level_idx) # In this case we have to verify that the 'previous level' is not the current one due to `end_of_accrual` level_start_date = self.date_from + get_timedelta(current_level.start_count, current_level.start_type) previous_level = level_ids[current_level_idx - 1] # If the next date from the current level's start date is before the last call of the previous level # return the previous level if current_level._get_next_date(level_start_date) < previous_level._get_next_date(level_start_date): return (previous_level, current_level_idx - 1) return (current_level, current_level_idx) def _process_accrual_plan_level(self, level, start_period, start_date, end_period, end_date): """ Returns the added days for that level """ self.ensure_one() if level.is_based_on_worked_time: start_dt = datetime.combine(start_date, datetime.min.time()) end_dt = datetime.combine(end_date, datetime.max.time()) worked = self.employee_id._get_work_days_data_batch(start_dt, end_dt, calendar=self.employee_id.resource_calendar_id)\ [self.employee_id.id]['hours'] left = self.employee_id.sudo()._get_leave_days_data_batch(start_dt, end_dt, domain=[('time_type', '=', 'leave')])[self.employee_id.id]['hours'] work_entry_prorata = worked / (left + worked) if worked else 0 added_value = work_entry_prorata * level.added_value else: added_value = level.added_value # Convert time in hours to time in days in case the level is encoded in hours if level.added_value_type == 'hours': added_value = added_value / (self.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY) period_prorata = 1 if start_period != start_date or end_period != end_date: period_days = (end_period - start_period) call_days = (end_date - start_date) period_prorata = min(1, call_days / period_days) if period_days else 1 return added_value * period_prorata def _process_accrual_plans(self): """ This method is part of the cron's process. The goal of this method is to retroactively apply accrual plan levels and progress from nextcall to today """ today = fields.Date.today() first_allocation = _("""This allocation have already ran once, any modification won't be effective to the days allocated to the employee. If you need to change the configuration of the allocation, cancel and create a new one.""") for allocation in self: level_ids = allocation.accrual_plan_id.level_ids.sorted('sequence') if not level_ids: continue if not allocation.nextcall: first_level = level_ids[0] first_level_start_date = allocation.date_from + get_timedelta(first_level.start_count, first_level.start_type) if today < first_level_start_date: # Accrual plan is not configured properly or has not started continue allocation.lastcall = max(allocation.lastcall, first_level_start_date) allocation.nextcall = first_level._get_next_date(allocation.lastcall) allocation._message_log(body=first_allocation) days_added_per_level = defaultdict(lambda: 0) while allocation.nextcall <= today: (current_level, current_level_idx) = allocation._get_current_accrual_plan_level_id(allocation.nextcall) nextcall = current_level._get_next_date(allocation.nextcall) # Since _get_previous_date returns the given date if it corresponds to a call date # this will always return lastcall except possibly on the first call # this is used to prorate the first number of days given to the employee period_start = current_level._get_previous_date(allocation.lastcall) period_end = current_level._get_next_date(allocation.lastcall) # Also prorate this accrual in the event that we are passing from one level to another if current_level_idx < (len(level_ids) - 1) and allocation.accrual_plan_id.transition_mode == 'immediately': next_level = level_ids[current_level_idx + 1] current_level_last_date = allocation.date_from + get_timedelta(next_level.start_count, next_level.start_type) - relativedelta(days=1) if allocation.nextcall != current_level_last_date: nextcall = min(nextcall, current_level_last_date) days_added_per_level[current_level] += allocation._process_accrual_plan_level( current_level, period_start, allocation.lastcall, period_end, allocation.nextcall) allocation.lastcall = allocation.nextcall allocation.nextcall = nextcall if days_added_per_level: number_of_days_to_add = 0 for value in days_added_per_level.values(): number_of_days_to_add += value # Let's assume the limit of the last level is the correct one allocation.write({'number_of_days': min(allocation.number_of_days + number_of_days_to_add, current_level.maximum_leave)}) @api.model def _update_accrual(self): """ Method called by the cron task in order to increment the number_of_days when necessary. """ # Get the current date to determine the start and end of the accrual period today = datetime.combine(fields.Date.today(), time(0, 0, 0)) if today.day == 1 and today.month == 1: end_of_year_allocations = self.search( [('allocation_type', '=', 'accrual'), ('state', '=', 'validate'), ('accrual_plan_id', '!=', False), ('employee_id', '!=', False), '|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now())]) end_of_year_allocations._end_of_year_accrual() end_of_year_allocations.flush() allocations = self.search( [('allocation_type', '=', 'accrual'), ('state', '=', 'validate'), ('accrual_plan_id', '!=', False), ('employee_id', '!=', False), '|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now()), '|', ('nextcall', '=', False), ('nextcall', '<=', today)]) allocations._process_accrual_plans() #################################################### # ORM Overrides methods #################################################### def onchange(self, values, field_name, field_onchange): # Try to force the leave_type name_get when creating new records # This is called right after pressing create and returns the name_get for # most fields in the view. if 'employee_id' in field_onchange: self = self.with_context(employee_id=int(field_onchange['employee_id'])) return super().onchange(values, field_name, field_onchange) def name_get(self): res = [] for allocation in self: if allocation.holiday_type == 'company': target = allocation.mode_company_id.name elif allocation.holiday_type == 'department': target = allocation.department_id.name elif allocation.holiday_type == 'category': target = allocation.category_id.name elif allocation.employee_id: target = allocation.employee_id.name else: target = ', '.join(allocation.employee_ids.sudo().mapped('name')) res.append( (allocation.id, _("Allocation of %(allocation_name)s : %(duration).2f %(duration_type)s to %(person)s", allocation_name=allocation.holiday_status_id.sudo().name, duration=allocation.number_of_hours_display if allocation.type_request_unit == 'hour' else allocation.number_of_days, duration_type=_('hours') if allocation.type_request_unit == 'hour' else _('days'), person=target )) ) return res def add_follower(self, employee_id): employee = self.env['hr.employee'].browse(employee_id) if employee.user_id: self.message_subscribe(partner_ids=employee.user_id.partner_id.ids) @api.model_create_multi def create(self, vals_list): """ Override to avoid automatic logging of creation """ for values in vals_list: employee_id = values.get('employee_id', False) if not values.get('department_id'): values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id}) # default `lastcall` to `nextcall` if 'date_from' in values and 'lastcall' not in values: values['lastcall'] = values['date_from'] holidays = super(HolidaysAllocation, self.with_context(mail_create_nosubscribe=True)).create(vals_list) for holiday in holidays: partners_to_subscribe = set() if holiday.employee_id.user_id: partners_to_subscribe.add(holiday.employee_id.user_id.partner_id.id) if holiday.validation_type == 'hr': partners_to_subscribe.add(holiday.employee_id.parent_id.user_id.partner_id.id) partners_to_subscribe.add(holiday.employee_id.leave_manager_id.partner_id.id) holiday.message_subscribe(partner_ids=tuple(partners_to_subscribe)) if not self._context.get('import_file'): holiday.activity_update() if holiday.validation_type == 'no': if holiday.state == 'draft': holiday.action_confirm() holiday.action_validate() return holidays def write(self, values): employee_id = values.get('employee_id', False) if values.get('state'): self._check_approval_update(values['state']) result = super(HolidaysAllocation, self).write(values) self.add_follower(employee_id) return result @api.ondelete(at_uninstall=False) def _unlink_if_correct_states(self): state_description_values = {elem[0]: elem[1] for elem in self._fields['state']._description_selection(self.env)} for holiday in self.filtered(lambda holiday: holiday.state not in ['draft', 'cancel', 'confirm']): raise UserError(_('You cannot delete an allocation request which is in %s state.') % (state_description_values.get(holiday.state),)) def _get_mail_redirect_suggested_company(self): return self.holiday_status_id.company_id #################################################### # Business methods #################################################### def _prepare_holiday_values(self, employees): self.ensure_one() return [{ 'name': self.name, 'holiday_type': 'employee', 'holiday_status_id': self.holiday_status_id.id, 'notes': self.notes, 'number_of_days': self.number_of_days, 'parent_id': self.id, 'employee_id': employee.id, 'employee_ids': [(6, 0, [employee.id])], 'state': 'confirm', 'allocation_type': self.allocation_type, 'date_from': self.date_from, 'date_to': self.date_to, 'accrual_plan_id': self.accrual_plan_id.id, } for employee in employees] def action_draft(self): if any(holiday.state not in ['confirm', 'refuse'] for holiday in self): raise UserError(_('Allocation request state must be "Refused" or "To Approve" in order to be reset to Draft.')) self.write({ 'state': 'draft', 'approver_id': False, }) linked_requests = self.mapped('linked_request_ids') if linked_requests: linked_requests.action_draft() linked_requests.unlink() self.activity_update() return True def action_confirm(self): if self.filtered(lambda holiday: holiday.state != 'draft'): raise UserError(_('Allocation request must be in Draft state ("To Submit") in order to confirm it.')) res = self.write({'state': 'confirm'}) self.activity_update() return res def action_validate(self): current_employee = self.env.user.employee_id if any(holiday.state != 'confirm' for holiday in self): raise UserError(_('Allocation request must be confirmed in order to approve it.')) self.write({ 'state': 'validate', 'approver_id': current_employee.id }) for holiday in self: holiday._action_validate_create_childs() self.activity_update() return True def _action_validate_create_childs(self): childs = self.env['hr.leave.allocation'] # In the case we are in holiday_type `employee` and there is only one employee we can keep the same allocation # Otherwise we do need to create an allocation for all employees to have a behaviour that is in line # with the other holiday_type if self.state == 'validate' and (self.holiday_type in ['category', 'department', 'company'] or (self.holiday_type == 'employee' and len(self.employee_ids) > 1)): if self.holiday_type == 'employee': employees = self.employee_ids elif self.holiday_type == 'category': employees = self.category_id.employee_ids elif self.holiday_type == 'department': employees = self.department_id.member_ids else: employees = self.env['hr.employee'].search([('company_id', '=', self.mode_company_id.id)]) allocation_create_vals = self._prepare_holiday_values(employees) childs += self.with_context( mail_notify_force_send=False, mail_activity_automation_skip=True ).create(allocation_create_vals) if childs: childs.action_validate() return childs def action_refuse(self): current_employee = self.env.user.employee_id if any(holiday.state not in ['confirm', 'validate', 'validate1'] for holiday in self): raise UserError(_('Allocation request must be confirmed or validated in order to refuse it.')) self.write({'state': 'refuse', 'approver_id': current_employee.id}) # If a category that created several holidays, cancel all related linked_requests = self.mapped('linked_request_ids') if linked_requests: linked_requests.action_refuse() self.activity_update() return True def _check_approval_update(self, state): """ Check if target state is achievable. """ if self.env.is_superuser(): return current_employee = self.env.user.employee_id if not current_employee: return is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user') is_manager = self.env.user.has_group('hr_holidays.group_hr_holidays_manager') for holiday in self: val_type = holiday.holiday_status_id.sudo().allocation_validation_type if state == 'confirm': continue if state == 'draft': if holiday.employee_id != current_employee and not is_manager: raise UserError(_('Only a time off Manager can reset other people allocation.')) continue if not is_officer and self.env.user != holiday.employee_id.leave_manager_id and not val_type == 'no': raise UserError(_('Only a time off Officer/Responsible or Manager can approve or refuse time off requests.')) if is_officer or self.env.user == holiday.employee_id.leave_manager_id: # use ir.rule based first access check: department, members, ... (see security.xml) holiday.check_access_rule('write') if holiday.employee_id == current_employee and not is_manager and not val_type == 'no': raise UserError(_('Only a time off Manager can approve its own requests.')) if (state == 'validate1' and val_type == 'both') or (state == 'validate' and val_type == 'manager'): if self.env.user == holiday.employee_id.leave_manager_id and self.env.user != holiday.employee_id.user_id: continue manager = holiday.employee_id.parent_id or holiday.employee_id.department_id.manager_id if (manager != current_employee) and not is_manager: raise UserError(_('You must be either %s\'s manager or time off manager to approve this time off') % (holiday.employee_id.name)) if state == 'validate' and val_type == 'both': if not is_officer: raise UserError(_('Only a Time off Approver can apply the second approval on allocation requests.')) @api.onchange('allocation_type') def _onchange_allocation_type(self): if self.allocation_type == 'accrual': self.number_of_days = 0.0 elif not self.number_of_days_display: self.number_of_days = 1.0 # ------------------------------------------------------------ # Activity methods # ------------------------------------------------------------ def _get_responsible_for_approval(self): self.ensure_one() responsible = self.env.user if self.validation_type == 'manager' or (self.validation_type == 'both' and self.state == 'confirm'): if self.employee_id.leave_manager_id: responsible = self.employee_id.leave_manager_id elif self.validation_type == 'hr' or (self.validation_type == 'both' and self.state == 'validate1'): if self.holiday_status_id.responsible_id: responsible = self.holiday_status_id.responsible_id return responsible def activity_update(self): to_clean, to_do = self.env['hr.leave.allocation'], self.env['hr.leave.allocation'] for allocation in self: note = _( 'New Allocation Request created by %(user)s: %(count)s Days of %(allocation_type)s', user=allocation.create_uid.name, count=allocation.number_of_days, allocation_type=allocation.holiday_status_id.name ) if allocation.state == 'draft': to_clean |= allocation elif allocation.state == 'confirm': allocation.activity_schedule( 'hr_holidays.mail_act_leave_allocation_approval', note=note, user_id=allocation.sudo()._get_responsible_for_approval().id or self.env.user.id) elif allocation.state == 'validate1': allocation.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval']) allocation.activity_schedule( 'hr_holidays.mail_act_leave_allocation_second_approval', note=note, user_id=allocation.sudo()._get_responsible_for_approval().id or self.env.user.id) elif allocation.state == 'validate': to_do |= allocation elif allocation.state == 'refuse': to_clean |= allocation if to_clean: to_clean.activity_unlink(['hr_holidays.mail_act_leave_allocation_approval', 'hr_holidays.mail_act_leave_allocation_second_approval']) if to_do: to_do.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval', 'hr_holidays.mail_act_leave_allocation_second_approval']) #################################################### # Messaging methods #################################################### def _track_subtype(self, init_values): if 'state' in init_values and self.state == 'validate': allocation_notif_subtype_id = self.holiday_status_id.allocation_notif_subtype_id return allocation_notif_subtype_id or self.env.ref('hr_holidays.mt_leave_allocation') return super(HolidaysAllocation, self)._track_subtype(init_values) def _notify_get_groups(self, msg_vals=None): """ Handle HR users and officers recipients that can validate or refuse holidays directly from email. """ groups = super(HolidaysAllocation, self)._notify_get_groups(msg_vals=msg_vals) local_msg_vals = dict(msg_vals or {}) self.ensure_one() hr_actions = [] if self.state == 'confirm': app_action = self._notify_get_action_link('controller', controller='/allocation/validate', **local_msg_vals) hr_actions += [{'url': app_action, 'title': _('Approve')}] if self.state in ['confirm', 'validate', 'validate1']: ref_action = self._notify_get_action_link('controller', controller='/allocation/refuse', **local_msg_vals) hr_actions += [{'url': ref_action, 'title': _('Refuse')}] holiday_user_group_id = self.env.ref('hr_holidays.group_hr_holidays_user').id new_group = ( 'group_hr_holidays_user', lambda pdata: pdata['type'] == 'user' and holiday_user_group_id in pdata['groups'], { 'actions': hr_actions, }) return [new_group] + groups def message_subscribe(self, partner_ids=None, subtype_ids=None): # due to record rule can not allow to add follower and mention on validated leave so subscribe through sudo if self.state in ['validate', 'validate1']: self.check_access_rights('read') self.check_access_rule('read') return super(HolidaysAllocation, self.sudo()).message_subscribe(partner_ids=partner_ids, subtype_ids=subtype_ids) return super(HolidaysAllocation, self).message_subscribe(partner_ids=partner_ids, subtype_ids=subtype_ids)
class asset_asset(models.Model): """ Assets """ _name = 'asset.asset' _description = 'Asset' _inherit = ['mail.thread'] def _read_group_state_ids(self, domain, read_group_order=None, access_rights_uid=None, team='3'): access_rights_uid = access_rights_uid or self.uid stage_obj = self.env['asset.state'] order = stage_obj._order # lame hack to allow reverting search, should just work in the trivial case if read_group_order == 'stage_id desc': order = "%s desc" % order # write the domain # - ('id', 'in', 'ids'): add columns that should be present # - OR ('team','=',team): add default columns that belongs team search_domain = [] search_domain += ['|', ('team','=',team)] search_domain += [('id', 'in', ids)] stage_ids = stage_obj._search(search_domain, order=order, access_rights_uid=access_rights_uid) result = stage_obj.name_get(access_rights_uid, stage_ids) # restore order of the search result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0]))) return result, {} def _read_group_finance_state_ids(self, domain, read_group_order=None, access_rights_uid=None): return self._read_group_state_ids(domain, read_group_order, access_rights_uid, '0') def _read_group_warehouse_state_ids(self, domain, read_group_order=None, access_rights_uid=None): return self._read_group_state_ids(domain, read_group_order, access_rights_uid, '1') def _read_group_manufacture_state_ids(self, domain, read_group_order=None, access_rights_uid=None): return self._read_group_state_ids(domain, read_group_order, access_rights_uid, '2') def _read_group_maintenance_state_ids(self, domain, read_group_order=None, access_rights_uid=None): return self._read_group_state_ids(domain, read_group_order, access_rights_uid, '3') def _read_group_accounting_state_ids(self, domain, read_group_order=None, access_rights_uid=None): return self._read_group_state_ids(domain, read_group_order, access_rights_uid, '4') CRITICALITY_SELECTION = [ ('0', 'General'), ('1', 'Important'), ('2', 'Very important'), ('3', 'Critical') ] name = fields.Char('Asset Name', size=64, required=True, translate=True) finance_state_id = fields.Many2one('asset.state', 'State', domain=[('team','=','0')]) warehouse_state_id = fields.Many2one('asset.state', 'State', domain=[('team','=','1')]) manufacture_state_id = fields.Many2one('asset.state', 'State', domain=[('team','=','2')]) maintenance_state_id = fields.Many2one('asset.state', 'State', domain=[('team','=','3')]) accounting_state_id = fields.Many2one('asset.state', 'State', domain=[('team','=','4')]) maintenance_state_color = fields.Selection(related='maintenance_state_id.state_color', selection=STATE_COLOR_SELECTION, string="Color", readonly=True) criticality = fields.Selection(CRITICALITY_SELECTION, 'Criticality') property_stock_asset = fields.Many2one( 'stock.location', "Asset Location", company_dependent=True, domain=[('usage', 'like', 'asset')], help="This location will be used as the destination location for installed parts during asset life.") user_id = fields.Many2one('res.users', 'Assigned to', track_visibility='onchange') active = fields.Boolean('Active', default=True) asset_number = fields.Char('Asset Number', size=64) model = fields.Char('Model', size=64) serial = fields.Char('Serial no.', size=64) vendor_id = fields.Many2one('res.partner', 'Vendor') manufacturer_id = fields.Many2one('res.partner', 'Manufacturer') start_date = fields.Date('Start Date') purchase_date = fields.Date('Purchase Date') warranty_start_date = fields.Date('Warranty Start') warranty_end_date = fields.Date('Warranty End') image = fields.Binary("Image") image_small = fields.Binary("Small-sized image") image_medium = fields.Binary("Medium-sized image") category_ids = fields.Many2many('asset.category', id1='asset_id', id2='category_id', string='Tags') _group_by_full = { 'finance_state_id': _read_group_finance_state_ids, 'warehouse_state_id': _read_group_warehouse_state_ids, 'manufacture_state_id': _read_group_manufacture_state_ids, 'maintenance_state_id': _read_group_maintenance_state_ids, 'accounting_state_id': _read_group_accounting_state_ids, } @api.model def create(self, vals): if 'image' in vals: vals['image_small'] = vals['image_medium'] = vals['image'] return super(asset_asset, self).create(vals) def write(self, vals): if 'image' in vals: vals['image_small'] = vals['image_medium'] = vals['image'] return super(asset_asset, self).write(vals)
class AccountPayment(models.Model): _name = "account.payment" _inherit = ['mail.thread', 'account.payment'] state = fields.Selection(track_visibility='always') amount = fields.Monetary(track_visibility='always') partner_id = fields.Many2one(track_visibility='always') journal_id = fields.Many2one(track_visibility='always') destination_journal_id = fields.Many2one(track_visibility='always') currency_id = fields.Many2one(track_visibility='always') # campo a ser extendido y mostrar un nombre detemrinado en las lineas de # pago de un payment group o donde se desee (por ej. con cheque, retención, # etc) payment_method_description = fields.Char( compute='_compute_payment_method_description', string='Payment Method', ) @api.multi def _compute_payment_method_description(self): for rec in self: rec.payment_method_description = rec.payment_method_id.display_name # nuevo campo funcion para definir dominio de los metodos payment_method_ids = fields.Many2many( 'account.payment.method', compute='_compute_payment_methods' ) journal_ids = fields.Many2many( 'account.journal', compute='_compute_journals' ) # journal_at_least_type = fields.Char( # compute='_compute_journal_at_least_type' # ) destination_journal_ids = fields.Many2many( 'account.journal', compute='_compute_destination_journals' ) @api.multi @api.depends( # 'payment_type', 'journal_id', ) def _compute_destination_journals(self): for rec in self: domain = [ ('type', 'in', ('bank', 'cash')), # al final pensamos mejor no agregar esta restricción, por ej, # para poder transferir a tarjeta a pagar. Esto solo se usa # en transferencias # ('at_least_one_inbound', '=', True), ('company_id', '=', rec.journal_id.company_id.id), ('id', '!=', rec.journal_id.id), ] rec.destination_journal_ids = rec.journal_ids.search(domain) # @api.multi # @api.depends( # 'payment_type', # ) # def _compute_journal_at_least_type(self): # for rec in self: # if rec.payment_type == 'inbound': # journal_at_least_type = 'at_least_one_inbound' # else: # journal_at_least_type = 'at_least_one_outbound' # rec.journal_at_least_type = journal_at_least_type @api.multi def get_journals_domain(self): """ We get domain here so it can be inherited """ self.ensure_one() domain = [('type', 'in', ('bank', 'cash'))] if self.payment_type == 'inbound': domain.append(('at_least_one_inbound', '=', True)) # Al final dejamos que para transferencias se pueda elegir # cualquier sin importar si tiene outbound o no # else: elif self.payment_type == 'outbound': domain.append(('at_least_one_outbound', '=', True)) return domain @api.multi @api.depends( 'payment_type', ) def _compute_journals(self): for rec in self: rec.journal_ids = rec.journal_ids.search(rec.get_journals_domain()) @api.multi @api.depends( 'journal_id.outbound_payment_method_ids', 'journal_id.inbound_payment_method_ids', 'payment_type', ) def _compute_payment_methods(self): for rec in self: if rec.payment_type in ('outbound', 'transfer'): methods = rec.journal_id.outbound_payment_method_ids else: methods = rec.journal_id.inbound_payment_method_ids rec.payment_method_ids = methods @api.onchange('payment_type') def _onchange_payment_type(self): """ Sobre escribimos y desactivamos la parte del dominio de la funcion original ya que se pierde si se vuelve a entrar """ if not self.invoice_ids: # Set default partner type for the payment type if self.payment_type == 'inbound': self.partner_type = 'customer' elif self.payment_type == 'outbound': self.partner_type = 'supplier' else: self.partner_type = False # limpiamos journal ya que podria no estar disponible para la nueva # operacion y ademas para que se limpien los payment methods self.journal_id = False # # Set payment method domain # res = self._onchange_journal() # if not res.get('domain', {}): # res['domain'] = {} # res['domain']['journal_id'] = self.payment_type == 'inbound' and [ # ('at_least_one_inbound', '=', True)] or [ # ('at_least_one_outbound', '=', True)] # res['domain']['journal_id'].append(('type', 'in', ('bank', 'cash'))) # return res # @api.onchange('partner_type') def _onchange_partner_type(self): """ Agregasmos dominio en vista ya que se pierde si se vuelve a entrar Anulamos funcion original porque no haria falta """ return True def _onchange_amount(self): """ Anulamos este onchange que termina cambiando el domain de journals y no es compatible con multicia y se pierde al guardar. TODO: ver que odoo con este onchange llama a _compute_journal_domain_and_types quien devolveria un journal generico cuando el importe sea cero, imagino que para hacer ajustes por diferencias de cambio """ return True @api.onchange('journal_id') def _onchange_journal(self): """ Sobre escribimos y desactivamos la parte del dominio de la funcion original ya que se pierde si se vuelve a entrar TODO: ver que odoo con este onchange llama a _compute_journal_domain_and_types quien devolveria un journal generico cuando el importe sea cero, imagino que para hacer ajustes por diferencias de cambio """ if self.journal_id: self.currency_id = ( self.journal_id.currency_id or self.company_id.currency_id) # Set default payment method # (we consider the first to be the default one) payment_methods = ( self.payment_type == 'inbound' and self.journal_id.inbound_payment_method_ids or self.journal_id.outbound_payment_method_ids) # si es una transferencia y no hay payment method de origen, # forzamos manual if not payment_methods and self.payment_type == 'transfer': payment_methods = self.env.ref( 'account.account_payment_method_manual_out') self.payment_method_id = ( payment_methods and payment_methods[0] or False) # si se eligió de origen el mismo diario de destino, lo resetiamos if self.journal_id == self.destination_journal_id: self.destination_journal_id = False # # Set payment method domain # # (restrict to methods enabled for the journal and to selected # # payment type) # payment_type = self.payment_type in ( # 'outbound', 'transfer') and 'outbound' or 'inbound' # return { # 'domain': { # 'payment_method_id': [ # ('payment_type', '=', payment_type), # ('id', 'in', payment_methods.ids)]}} # return {} @api.multi @api.depends('invoice_ids', 'payment_type', 'partner_type', 'partner_id') def _compute_destination_account_id(self): """ We send force_company on context so payments can be created from parent companies. We try to send force_company on self but it doesnt works, it only works sending it on partner """ res = super(AccountPayment, self)._compute_destination_account_id() for rec in self.filtered( lambda x: not x.invoice_ids and x.payment_type != 'transfer'): partner = self.partner_id.with_context( force_company=self.company_id.id) if self.partner_type == 'customer': self.destination_account_id = ( partner.property_account_receivable_id.id) else: self.destination_account_id = ( partner.property_account_payable_id.id) return res
class SyncTask(models.Model): _name = 'cus_args.sync_task' _rec_name = 'sync_model' _description = 'Data Sync Task' scheme = fields.Selection( string="Scheme", selection=[ ('http', 'Http'), ('https', 'Https'), ], required=True, ) base_url = fields.Char( string="Source URL", required=True, ) data_uri = fields.Char( string="URI", required=True, ) sync_model = fields.Char( string="Model", required=True, ) port = fields.Char( string="Port", required=True, ) @api.multi def sync_data(self): """同步数据""" print(u"开始同步数据!") if self.sync_model not in [ 'cus_args.goods_tariff', 'cus_args.goods_declare_element', 'cus_args.register_company' ]: self.general_data() elif self.sync_model in ['cus_args.goods_tariff']: self.goods_tariff_data() elif self.sync_model in ['cus_args.goods_declare_element']: self.declare_element_data() elif self.sync_model in ['cus_args.register_company']: self.register_company_data() return True @api.multi def general_data(self): records = self.env[self.sync_model].search([]) if not records: codes = {} else: codes = records.mapped(lambda r: {r.code: r.id}) codes_dic = {} for dic in codes: codes_dic.update(dic) codes = codes_dic print(codes) source_url = self.scheme + '://' + self.base_url + ':' + self.port + '/' + self.data_uri print(source_url) rest_codes = set() while (source_url): response = requests.get(source_url) print('response code: {}'.format(response.status_code)) response.raise_for_status() response = response.json() source_url = response.get('next') for item in response.get('results'): code = item.get('code') rest_codes.add(code) if code not in codes: self.env[self.sync_model].create({ 'code': code, 'name_cn': item.get('name_cn') }) else: self.env[self.sync_model].browse(codes[code]).write( {'name_cn': item.get('name_cn')}) rm_codes = set(codes.keys()) - rest_codes if rm_codes: rm_obj_ids = [codes[obj_id] for obj_id in rm_codes] rm_objs = self.env[self.sync_model].search([('id', 'in', rm_obj_ids)]) rm_objs.unlink() @api.multi def goods_tariff_data(self): records = self.env[self.sync_model].search([]) if not records: codes = {} else: codes = records.mapped(lambda r: {r.code_ts: r.id}) codes_dic = {} for dic in codes: codes_dic.update(dic) codes = codes_dic print(codes) source_url = self.scheme + '://' + self.base_url + ':' + self.port + '/' + self.data_uri print(source_url) rest_codes = set() while (source_url): response = requests.get(source_url) print('response code: {}'.format(response.status_code)) response.raise_for_status() response = response.json() source_url = response.get('next') for item in response.get('results'): code = item.get('code_ts') rest_codes.add(code) if code not in codes: self.env[self.sync_model].create({ 'code_ts': code, 'code_t': item.get('code_t'), 'code_s': item.get('code_s'), 'first_unit_id': self.env['cus_args.unit'].search([ ('code', '=', item.get('first_unit')) ])[0].id if item.get('first_unit') else False, 'second_unit_id': self.env['cus_args.unit'].search([ ('code', '=', item.get('second_unit')) ])[0].id if item.get('second_unit') else False, 'name_cn': item.get('name_cn'), 'supervision_condition': item.get('supervision_condition') }) else: self.env[self.sync_model].browse(codes[code]).write({ 'name_cn': item.get('name_cn'), 'code_t': item.get('code_t'), 'code_s': item.get('code_s'), 'first_unit_id': self.env['cus_args.unit'].search([ ('code', '=', item.get('first_unit')) ])[0].id if item.get('first_unit') else False, 'second_unit_id': self.env['cus_args.unit'].search([ ('code', '=', item.get('second_unit')) ])[0].id if item.get('second_unit') else False, 'supervision_condition': item.get('supervision_condition') }) rm_codes = set(codes.keys()) - rest_codes if rm_codes: rm_obj_ids = [codes[obj_id] for obj_id in rm_codes] rm_objs = self.env[self.sync_model].search([('id', 'in', rm_obj_ids)]) rm_objs.unlink() @api.multi def declare_element_data(self): records = self.env[self.sync_model].search([]) records.unlink() source_url = self.scheme + '://' + self.base_url + ':' + self.port + '/' + self.data_uri print(source_url) while (source_url): response = requests.get(source_url) print('response code: {}'.format(response.status_code)) response.raise_for_status() response = response.json() source_url = response.get('next') for item in response.get('results'): self.env[self.sync_model].create({ 'goods_tariff_hs_code': item['cus_goods_tariff_id'], 'name_cn': item.get('name'), 'sequence': item.get('sequence') }) @api.multi def register_company_data(self): records = self.env[self.sync_model].search([]) if not records: codes = {} else: codes = records.mapped(lambda r: {r.register_code: r.id}) codes_dic = {} for dic in codes: codes_dic.update(dic) codes = codes_dic print(codes) source_url = self.scheme + '://' + self.base_url + ':' + self.port + '/' + self.data_uri print(source_url) rest_codes = set() while (source_url): response = requests.get(source_url) print('response code: {}'.format(response.status_code)) response.raise_for_status() response = response.json() source_url = response.get('next') for item in response.get('results'): code = item.get('register_code') rest_codes.add(code) if code not in codes: self.env[self.sync_model].create({ 'register_code': code, 'register_name_cn': item.get('register_name_cn'), 'unified_social_credit_code': item.get('unified_social_credit_code') }) else: self.env[self.sync_model].browse(codes[code]).write({ 'register_name_cn': item.get('register_name_cn'), 'unified_social_credit_code': item.get('unified_social_credit_code') }) rm_codes = set(codes.keys()) - rest_codes if rm_codes: rm_obj_ids = [codes[obj_id] for obj_id in rm_codes] rm_objs = self.env[self.sync_model].search([('id', 'in', rm_obj_ids)]) rm_objs.unlink()
class AccountPaymentMethod(models.Model): _inherit = "account.payment.method" name = fields.Char(translate=True)
class urgence(models.Model): _name = "purchase.exp.besoin.urgence" _description = "urgence" name = fields.Char('Libellé', size=64, required=True, readonly=False)
class TaxReportWizard(models.TransientModel): _name = "tax.report.wizard" excel_file = fields.Binary('Sales Report') file_name = fields.Char('Excel File', size=64)