class Inventory(models.Model): _name = "stock.inventory" _description = "Inventory" @api.model def _default_location_id(self): company_user = self.env.user.company_id warehouse = self.env['stock.warehouse'].search([('company_id', '=', company_user.id)], limit=1) if warehouse: return warehouse.lot_stock_id.id else: raise UserError(_('You must define a warehouse for the company: %s.') % (company_user.name,)) name = fields.Char( 'Inventory Reference', readonly=True, required=True, states={'draft': [('readonly', False)]}) date = fields.Datetime( 'Inventory Date', readonly=True, required=True, default=fields.Datetime.now, help="The date that will be used for the stock level check of the products and the validation of the stock move related to this inventory.") line_ids = fields.One2many( 'stock.inventory.line', 'inventory_id', string='Inventories', copy=True, readonly=False, states={'done': [('readonly', True)]}) move_ids = fields.One2many( 'stock.move', 'inventory_id', string='Created Moves', states={'done': [('readonly', True)]}) state = fields.Selection(string='Status', selection=[ ('draft', 'Draft'), ('cancel', 'Cancelled'), ('confirm', 'In Progress'), ('done', 'Validated')], copy=False, index=True, readonly=True, default='draft') company_id = fields.Many2one( 'res.company', 'Company', readonly=True, index=True, required=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['res.company']._company_default_get('stock.inventory')) location_id = fields.Many2one( 'stock.location', 'Inventoried Location', readonly=True, required=True, states={'draft': [('readonly', False)]}, default=_default_location_id) product_id = fields.Many2one( 'product.product', 'Inventoried Product', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Product to focus your inventory on a particular Product.") package_id = fields.Many2one( 'stock.quant.package', 'Inventoried Pack', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Pack to focus your inventory on a particular Pack.") partner_id = fields.Many2one( 'res.partner', 'Inventoried Owner', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Owner to focus your inventory on a particular Owner.") lot_id = fields.Many2one( 'stock.production.lot', 'Inventoried Lot/Serial Number', copy=False, readonly=True, states={'draft': [('readonly', False)]}, help="Specify Lot/Serial Number to focus your inventory on a particular Lot/Serial Number.") filter = fields.Selection( string='Inventory of', selection='_selection_filter', required=True, default='none', help="If you do an entire inventory, you can choose 'All Products' and it will prefill the inventory with the current stock. If you only do some products " "(e.g. Cycle Counting) you can choose 'Manual Selection of Products' and the system won't propose anything. You can also let the " "system propose for a single product / lot /... ") total_qty = fields.Float('Total Quantity', compute='_compute_total_qty') category_id = fields.Many2one( 'product.category', 'Inventoried Category', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Product Category to focus your inventory on a particular Category.") exhausted = fields.Boolean('Include Exhausted Products', readonly=True, states={'draft': [('readonly', False)]}) @api.one @api.depends('product_id', 'line_ids.product_qty') def _compute_total_qty(self): """ For single product inventory, total quantity of the counted """ if self.product_id: self.total_qty = sum(self.mapped('line_ids').mapped('product_qty')) else: self.total_qty = 0 @api.model def _selection_filter(self): """ Get the list of filter allowed according to the options checked in 'Settings\Warehouse'. """ res_filter = [ ('none', _('All products')), ('category', _('One product category')), ('product', _('One product only')), ('partial', _('Select products manually'))] if self.user_has_groups('stock.group_tracking_owner'): res_filter += [('owner', _('One owner only')), ('product_owner', _('One product for a specific owner'))] if self.user_has_groups('stock.group_production_lot'): res_filter.append(('lot', _('One Lot/Serial Number'))) if self.user_has_groups('stock.group_tracking_lot'): res_filter.append(('pack', _('A Pack'))) return res_filter @api.onchange('filter') def onchange_filter(self): if self.filter not in ('product', 'product_owner'): self.product_id = False if self.filter != 'lot': self.lot_id = False if self.filter not in ('owner', 'product_owner'): self.partner_id = False if self.filter != 'pack': self.package_id = False if self.filter != 'category': self.category_id = False if self.filter == 'product': self.exhausted = True @api.onchange('location_id') def onchange_location_id(self): if self.location_id.company_id: self.company_id = self.location_id.company_id @api.one @api.constrains('filter', 'product_id', 'lot_id', 'partner_id', 'package_id') def _check_filter_product(self): if self.filter == 'none' and self.product_id and self.location_id and self.lot_id: return if self.filter not in ('product', 'product_owner') and self.product_id: raise UserError(_('The selected inventory options are not coherent.')) if self.filter != 'lot' and self.lot_id: raise UserError(_('The selected inventory options are not coherent.')) if self.filter not in ('owner', 'product_owner') and self.partner_id: raise UserError(_('The selected inventory options are not coherent.')) if self.filter != 'pack' and self.package_id: raise UserError(_('The selected inventory options are not coherent.')) @api.multi def action_reset_product_qty(self): self.mapped('line_ids').write({'product_qty': 0}) return True reset_real_qty = action_reset_product_qty @api.multi def action_done(self): negative = next((line for line in self.mapped('line_ids') if line.product_qty < 0 and line.product_qty != line.theoretical_qty), False) if negative: raise UserError(_('You cannot set a negative product quantity in an inventory line:\n\t%s - qty: %s') % (negative.product_id.name, negative.product_qty)) self.action_check() self.write({'state': 'done'}) self.post_inventory() return True @api.multi def post_inventory(self): # The inventory is posted as a single step which means quants cannot be moved from an internal location to another using an inventory # as they will be moved to inventory loss, and other quants will be created to the encoded quant location. This is a normal behavior # as quants cannot be reuse from inventory location (users can still manually move the products before/after the inventory if they want). self.mapped('move_ids').filtered(lambda move: move.state != 'done').action_done() @api.multi def action_check(self): """ Checks the inventory and computes the stock move to do """ # tde todo: clean after _generate_moves for inventory in self: # first remove the existing stock moves linked to this inventory inventory.mapped('move_ids').unlink() for line in inventory.line_ids: # compare the checked quantities on inventory lines to the theorical one stock_move = line._generate_moves() @api.multi def action_cancel_draft(self): self.mapped('move_ids').action_cancel() self.write({ 'line_ids': [(5,)], 'state': 'draft' }) @api.multi def action_start(self): for inventory in self: vals = {'state': 'confirm', 'date': fields.Datetime.now()} if (inventory.filter != 'partial') and not inventory.line_ids: vals.update({'line_ids': [(0, 0, line_values) for line_values in inventory._get_inventory_lines_values()]}) inventory.write(vals) return True prepare_inventory = action_start @api.multi def _get_inventory_lines_values(self): # TDE CLEANME: is sql really necessary ? I don't think so locations = self.env['stock.location'].search([('id', 'child_of', [self.location_id.id])]) domain = ' location_id in %s' args = (tuple(locations.ids),) vals = [] Product = self.env['product.product'] # Empty recordset of products available in stock_quants quant_products = self.env['product.product'] # Empty recordset of products to filter products_to_filter = self.env['product.product'] # case 0: Filter on company if self.company_id: domain += ' AND company_id = %s' args += (self.company_id.id,) #case 1: Filter on One owner only or One product for a specific owner if self.partner_id: domain += ' AND owner_id = %s' args += (self.partner_id.id,) #case 2: Filter on One Lot/Serial Number if self.lot_id: domain += ' AND lot_id = %s' args += (self.lot_id.id,) #case 3: Filter on One product if self.product_id: domain += ' AND product_id = %s' args += (self.product_id.id,) products_to_filter |= self.product_id #case 4: Filter on A Pack if self.package_id: domain += ' AND package_id = %s' args += (self.package_id.id,) #case 5: Filter on One product category + Exahausted Products if self.category_id: categ_products = Product.search([('categ_id', '=', self.category_id.id)]) domain += ' AND product_id = ANY (%s)' args += (categ_products.ids,) products_to_filter |= categ_products self.env.cr.execute("""SELECT product_id, sum(qty) as product_qty, location_id, lot_id as prod_lot_id, package_id, owner_id as partner_id FROM stock_quant WHERE %s GROUP BY product_id, location_id, lot_id, package_id, partner_id """ % domain, args) for product_data in self.env.cr.dictfetchall(): # replace the None the dictionary by False, because falsy values are tested later on for void_field in [item[0] for item in product_data.items() if item[1] is None]: product_data[void_field] = False product_data['theoretical_qty'] = product_data['product_qty'] if product_data['product_id']: product_data['product_uom_id'] = Product.browse(product_data['product_id']).uom_id.id quant_products |= Product.browse(product_data['product_id']) vals.append(product_data) if self.exhausted: exhausted_vals = self._get_exhausted_inventory_line(products_to_filter, quant_products) vals.extend(exhausted_vals) return vals def _get_exhausted_inventory_line(self, products, quant_products): ''' This function return inventory lines for exausted products :param products: products With Selected Filter. :param quant_products: products available in stock_quants ''' vals = [] exhausted_domain = [('type', 'not in', ('service', 'consu', 'digital'))] if products: exhausted_products = products - quant_products exhausted_domain += [('id', 'in', exhausted_products.ids)] else: exhausted_domain += [('id', 'not in', quant_products.ids)] exhausted_products = self.env['product.product'].search(exhausted_domain) for product in exhausted_products: vals.append({ 'inventory_id': self.id, 'product_id': product.id, 'location_id': self.location_id.id, }) return vals
class HrPayslipYear(models.Model): _name = 'hr.payslip.year' salary_rule_id = fields.Many2one('hr.salary.rule', string="Rule Name") total = fields.Float(string="Amount") hr_payslip_id = fields.Many2one('hr.payslip', string="Current Payslip Id")
class RoyaltyFeeLinestoInvoice(models.TransientModel): _name = 'royalty.fee.lines.to.invoice' royalty_fee_lines_id = fields.Many2one('royalty.fee.to.invoice') month = fields.Many2one('royalty.fee.month', string="Month") royalty_fee = fields.Float(string="Royalty Fee")
class investasi(models.Model): _name = 'anggaran.investasi' name = fields.Char('Nomor', required=True, readonly=True) tanggal = fields.Date('Tanggal', required=True) unit_id = fields.Many2one('anggaran.unit', 'Unit Kerja') tahun_id = fields.Many2one('anggaran.fiscalyear', 'Tahun') period_id = fields.Many2one('anggaran.period', 'Perioda') # keperluan = fields.Many2one('anggaran.rka_kegiatan', 'Untuk Keperluan') kepada_partner_id = fields.Many2one( 'res.partner', 'Dibayarkan Kepada', help="Partner (Supplier/Perorangan) penerima") keperluan = fields.Text("Keperluan") total = fields.Float("Total Biaya") 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) user_id = fields.Many2one('res.users', 'Created', required=True, readonly=True) state = fields.Selection(INVESTASI_STATES, 'Status', readonly=True, required=True) _defaults = { 'state': INVESTASI_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 action_draft(self, cr, uid, ids, context=None): #set to "draft" state return self.write(cr, uid, ids, {'state': INVESTASI_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': INVESTASI_STATES[1][0]}, context=context) def action_reject(self, cr, uid, ids, context=None): #set to "done" state return self.write(cr, uid, ids, {'state': INVESTASI_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': INVESTASI_STATES[3][0]}, context=context) def create(self, vals): #if context is None: # context = {} if vals.get('name', '/') == '/': vals['name'] = self.env['ir.sequence'].get( 'anggaran.investasi') or '/' new_id = super(investasi, self).create(vals) return new_id
class IqamaRenew(models.Model): _name = 'iqama.renew' _rec_name = 'order_number' _inherit = ['mail.thread', 'mail.activity.mixin'] order_number = fields.Char(string="Order Number", readonly=True) employee_id = fields.Many2one('hr.employee', string="Employee") job_number = fields.Char(string="Job Number", ) job_title = fields.Char("Job Title", ) department_id = fields.Many2one('hr.department', string="Department") iqama_end_date = fields.Date(string="iqama_end_date") iqama_number = fields.Char("IQAMA NUMBER", related="employee_id.identification_id") iqama_newstart_date = fields.Date(string="بداية الاقامة الجديدة", required=True) iqama_newend_date = fields.Date(string=" نهاية الاقامة الجديدة", required=True) reason_refuse = fields.Char(string="سبب الرفض") deferred_expense_models = fields.Many2one('account.asset', string="Deferred Expense Models") deferred_expense = fields.Many2one('account.asset', string="Deferred Expense") date_payed = fields.Date(string="تاريخ الدفع") amount = fields.Float(string="القيمة", required=True) journal_id = fields.Many2one('account.journal', string="اليوميه") account_id = fields.Many2one('account.account', string="الحساب") move_id = fields.Many2one('account.move', string="القيد", readonly=True) analytic_account_id = fields.Many2one( 'account.analytic.account', 'Analytic Account', ) state = fields.Selection([ ('draft', 'Draft'), ('hr_manager_accept', 'Hr Manager Approved'), ('finance_manager', 'Finance Manager Approved'), ('director', 'Director Approved'), ('accepted', 'POSTED'), ('cancel', 'Cancelled'), ("refuse", "Refuse"), ], string='Status', default='draft') @api.model def create_activity_type(self): modelid = (self.env['ir.model'].search([('model', '=', 'iqama.renew') ])).id advanced_salary = (self.env['ir.model'].search([ ('model', '=', 'advanced.salary') ])).id advanced_salary_monthly = (self.env['ir.model'].search([ ('model', '=', 'advanced.salary.monthly') ])).id penalty_salary = (self.env['ir.model'].search([('model', '=', 'penalty.salary')])).id exit_entry = (self.env['ir.model'].search([('model', '=', 'exit.entry') ])).id end_employee = (self.env['ir.model'].search([('model', '=', 'end.employee')])).id activity = self.env['mail.activity.type'] activity.create({ 'name': 'Iqama Renew', 'res_model_id': modelid, 'delay_unit': 'days', 'delay_from': 'previous_activity', }) activity.create({ 'name': 'Advanced Salary ', 'res_model_id': advanced_salary, 'delay_unit': 'days', 'delay_from': 'previous_activity', }) activity.create({ 'name': 'Advanced Salary monthly', 'res_model_id': advanced_salary_monthly, 'delay_unit': 'days', 'delay_from': 'previous_activity', }) activity.create({ 'name': 'Penalty Salary', 'res_model_id': penalty_salary, 'delay_unit': 'days', 'delay_from': 'previous_activity', }) activity.create({ 'name': 'Exit entry', 'res_model_id': exit_entry, 'delay_unit': 'days', 'delay_from': 'previous_activity', }) activity.create({ 'name': 'End employee', 'res_model_id': end_employee, 'delay_unit': 'days', 'delay_from': 'previous_activity', }) def draft_advanced(self): self.deferred_expense.set_to_draft() self.deferred_expense.unlink() self.move_id.button_draft() lines = self.env['account.move.line'].search([('move_id', '=', self.move_id.id)]) lines.unlink() modelid = (self.env['ir.model'].search([('model', '=', 'iqama.renew') ])).id select = "select uid from res_groups_users_rel as gs where gs.gid in (select id from res_groups as gg where name = '%s' and category_id in (select id from ir_module_category where name = '%s') ) " % ( 'Administrator', 'Employees') self.env.cr.execute(select) results = self.env.cr.dictfetchall() print("wwresults", results) users = [] for obj in results: users.append(obj['uid']) print("users", users) user_id = (self.env['res.users'].search([('id', 'in', users)])) activity = self.env['mail.activity'] for user in user_id: activity_ins = activity.create({ 'res_id': self.id, 'res_model_id': modelid, 'res_model': 'iqama.renew', 'activity_type_id': (self.env['mail.activity.type'].search([ ('res_model_id', '=', modelid) ])).id, 'summary': '', 'note': '', 'date_deadline': date.today(), 'activity_category': 'default', 'previous_activity_type_id': False, 'recommended_activity_type_id': False, 'user_id': user.id }) self.state = "draft" @api.model def create(self, vals): if 'order_number' not in vals: vals['order_number'] = self.env['ir.sequence'].next_by_code( 'iqama.renew') res = super(IqamaRenew, self).create(vals) modelid = (self.env['ir.model'].search([('model', '=', 'iqama.renew') ])).id select = "select uid from res_groups_users_rel as gs where gs.gid in (select id from res_groups as gg where name = '%s' and category_id in (select id from ir_module_category where name = '%s') ) " % ( 'Administrator', 'Employees') self.env.cr.execute(select) results = self.env.cr.dictfetchall() print("wwresults", results) users = [] for obj in results: users.append(obj['uid']) print("users", users) user_id = (self.env['res.users'].search([('id', 'in', users)])) activity = self.env['mail.activity'] for user in user_id: activity_ins = activity.create({ 'res_id': res.id, 'res_model_id': modelid, 'res_model': 'iqama.renew', 'activity_type_id': (res.env['mail.activity.type'].search([ ('res_model_id', '=', modelid) ])).id, 'summary': '', 'note': '', 'date_deadline': date.today(), 'activity_category': 'default', 'previous_activity_type_id': False, 'recommended_activity_type_id': False, 'user_id': user.id }) return res @api.onchange('employee_id') def onchange_employee(self): if self.employee_id: self.job_title = self.employee_id.job_id.name self.job_number = self.employee_id.job_number self.department_id = self.employee_id.department_id.id def action_finance_manager(self): if self.env.user.has_group('account.group_account_manager'): activity_old = (self.env['mail.activity'].search([ ('res_model', '=', 'iqama.renew'), ('res_id', '=', self.id) ])) for ac in activity_old: ac.action_done() modelid = (self.env['ir.model'].search([('model', '=', 'iqama.renew')])).id select = "select uid from res_groups_users_rel as gs where gs.gid in (select id from res_groups as gg where name = '%s' ) " % ( 'Director Group') self.env.cr.execute(select) results = self.env.cr.dictfetchall() print("wwresults", results) users = [] for obj in results: users.append(obj['uid']) print("users", users) user_id = (self.env['res.users'].search([('id', 'in', users)])) activity = self.env['mail.activity'] for user in user_id: activity_ins = activity.create({ 'res_id': self.id, 'res_model_id': modelid, 'res_model': 'iqama.renew', 'activity_type_id': (self.env['mail.activity.type'].search([ ('res_model_id', '=', modelid) ])).id, 'summary': '', 'note': '', 'date_deadline': date.today(), 'activity_category': 'default', 'previous_activity_type_id': False, 'recommended_activity_type_id': False, 'user_id': user.id }) return self.write({'state': 'finance_manager'}) else: raise UserError(_("لا يمكنك الموافقة صلاحية مسؤول المحاسبة")) def action_hr_manager_accept(self): if self.env.user.has_group('hr.group_hr_manager'): activity_old = (self.env['mail.activity'].search([ ('res_model', '=', 'iqama.renew'), ('res_id', '=', self.id) ])) for ac in activity_old: ac.action_done() modelid = (self.env['ir.model'].search([('model', '=', 'iqama.renew')])).id select = "select uid from res_groups_users_rel as gs where gs.gid in (select id from res_groups as gg where name = '%s' and category_id in (select id from ir_module_category where name = '%s') ) " % ( 'Advisor', 'Accounting') self.env.cr.execute(select) results = self.env.cr.dictfetchall() print("wwresults", results) users = [] for obj in results: users.append(obj['uid']) print("users", users) user_id = (self.env['res.users'].search([('id', 'in', users)])) activity = self.env['mail.activity'] for user in user_id: activity_ins = activity.create({ 'res_id': self.id, 'res_model_id': modelid, 'res_model': 'iqama.renew', 'activity_type_id': (self.env['mail.activity.type'].search([ ('res_model_id', '=', modelid) ])).id, 'summary': '', 'note': '', 'date_deadline': date.today(), 'activity_category': 'default', 'previous_activity_type_id': False, 'recommended_activity_type_id': False, 'user_id': user.id }) return self.write({'state': 'hr_manager_accept'}) else: raise UserError( _("لا يمكنك الموافقة صلاحية مدير الموارد البشرية فقط")) activity_old = (self.env['mail.activity'].search([ ('res_model', '=', 'iqama.renew'), ('res_id', '=', self.id) ])) for ac in activity_old: ac.action_done() def action_director(self): if self.env.user.has_group('payroll_edition.director_group_manager'): activity_old = (self.env['mail.activity'].search([ ('res_model', '=', 'iqama.renew'), ('res_id', '=', self.id) ])) for ac in activity_old: ac.action_done() modelid = (self.env['ir.model'].search([('model', '=', 'iqama.renew')])).id select = "select uid from res_groups_users_rel as gs where gs.gid in (select id from res_groups as gg where name = '%s' ) " % ( 'Accounting Agent') self.env.cr.execute(select) results = self.env.cr.dictfetchall() users = [] for obj in results: users.append(obj['uid']) user_id = (self.env['res.users'].search([('id', 'in', users)])) activity = self.env['mail.activity'] for user in user_id: activity_ins = activity.create({ 'res_id': self.id, 'res_model_id': modelid, 'res_model': 'iqama.renew', 'activity_type_id': (self.env['mail.activity.type'].search([ ('res_model_id', '=', modelid) ])).id, 'summary': '', 'note': '', 'date_deadline': date.today(), 'activity_category': 'default', 'previous_activity_type_id': False, 'recommended_activity_type_id': False, 'user_id': user.id }) return self.write({'state': 'director'}) else: raise UserError(_("لا يمكنك الموافقة صلاحية الادارة فقط")) def return_movelines(self): all_move_vals = [] for rec in self: move_vals = { 'date': rec.date_payed, 'ref': rec.order_number, 'journal_id': rec.journal_id.id, 'line_ids': [ (0, 0, { 'name': rec.order_number, 'debit': rec.amount, 'credit': 0.0, 'account_id': rec.account_id.id, 'analytic_account_id': rec.analytic_account_id.id, 'partner_id': rec.employee_id.address_home_id.id, }), (0, 0, { 'name': rec.order_number, 'debit': 0.0, 'credit': rec.amount, 'account_id': rec.journal_id.default_credit_account_id.id, 'analytic_account_id': rec.analytic_account_id.id, 'partner_id': rec.employee_id.address_home_id.id, }), ], } all_move_vals.append(move_vals) return all_move_vals def action_accepted(self): if self.env.user.has_group( 'payroll_edition.accounting_agent_group_manager' ) or self.env.user.has_group('account.group_account_manager'): if not self.account_id or not self.journal_id or not self.date_payed or not self.deferred_expense_models: raise UserError( _("يجب تعبئة الحقول المستخدمة في عملية انشاء القيود")) account_asset = self.env['account.asset'] res_asset = account_asset.create({ 'asset_type': 'expense', 'state': 'draft', 'name': self.deferred_expense_models.name, 'original_value': self.amount, 'acquisition_date': self.date_payed, 'method_number': self.deferred_expense_models.method_number, 'method_period': self.deferred_expense_models.method_period, 'account_depreciation_id': self.deferred_expense_models.account_depreciation_id.id, 'account_depreciation_expense_id': self.deferred_expense_models.account_depreciation_expense_id. id, 'journal_id': self.deferred_expense_models.journal_id.id, 'account_analytic_id': self.deferred_expense_models.account_analytic_id.id, 'analytic_tag_ids': self.deferred_expense_models.analytic_tag_ids.ids }) print("res_asset", res_asset) self.deferred_expense = res_asset.id self.deferred_expense.compute_depreciation_board() self.deferred_expense.validate() self.employee_id.iqama_start_date = self.iqama_newstart_date self.employee_id.iqama_end_date = self.iqama_newend_date AccountMove = self.env['account.move'].with_context( default_type='entry') if not self.move_id: moves = AccountMove.create(self.return_movelines()) moves.post() self.move_id = moves.id else: self.move_id.button_draft() self.move_id.date = self.date_payed move_line_vals = [] line1 = (0, 0, { 'name': self.order_number, 'debit': self.amount, 'credit': 0, 'account_id': self.account_id.id, 'partner_id': self.employee_id.address_home_id.id, 'analytic_account_id': self.analytic_account_id.id }) line2 = (0, 0, { 'name': self.order_number, 'debit': 0, 'credit': self.amount, 'account_id': self.journal_id.default_credit_account_id.id, 'partner_id': self.employee_id.address_home_id.id, 'analytic_account_id': self.analytic_account_id.id }) move_line_vals.append(line1) move_line_vals.append(line2) self.move_id.line_ids = move_line_vals self.move_id.post() activity_old = (self.env['mail.activity'].search([ ('res_model', '=', 'iqama.renew'), ('res_id', '=', self.id) ])) for ac in activity_old: ac.action_done() return self.write({'state': 'accepted'}) else: raise UserError(_("لا يمكنك الموافقة صلاحية الادارة فقط")) def refuse_go(self): if self.state == "draft": if self.env.user.has_group('hr.group_hr_manager'): self.state = "refuse" else: raise UserError( _("لا يمكنك الموافقة صلاحية مدير الموارد البشرية فقط")) elif self.state == "hr_manager_accept": if self.env.user.has_group('account.group_account_manager'): self.state = "refuse" else: raise UserError(_("لا يمكنك الموافقة صلاحية مسؤول المحاسبة")) elif self.state == "finance_manager": if self.env.user.has_group( 'payroll_edition.director_group_manager'): self.state = "refuse" else: raise UserError(_("لا يمكنك الموافقة صلاحية الادارة فقط")) if not self.reason_refuse: raise UserError(_("يجب تعبئة حقل سبب الرفض")) activity_old = (self.env['mail.activity'].search([ ('res_model', '=', 'exit.entry'), ('res_id', '=', self.id) ])) for ac in activity_old: ac.action_done() def action_cancel(self): activity_old = (self.env['mail.activity'].search([ ('res_model', '=', 'iqama.renew'), ('res_id', '=', self.id) ])) for ac in activity_old: ac.action_done() return self.write({'state': 'cancel'})
class MrpWorkcenter(models.Model): _name = 'mrp.workcenter' _description = 'Work Center' _order = "sequence, id" _inherit = ['resource.mixin'] # resource name = fields.Char(related='resource_id.name', store=True) time_efficiency = fields.Float('Time Efficiency', related='resource_id.time_efficiency', default=100, store=True) active = fields.Boolean('Active', related='resource_id.active', default=True, store=True) code = fields.Char('Code', copy=False) note = fields.Text('Description', help="Description of the Work Center.") capacity = fields.Float( 'Capacity', default=1.0, oldname='capacity_per_cycle', help="Number of pieces that can be produced in parallel.") sequence = fields.Integer( 'Sequence', default=1, required=True, help="Gives the sequence order when displaying a list of work centers." ) color = fields.Integer('Color') time_start = fields.Float('Time before prod.', help="Time in minutes for the setup.") time_stop = fields.Float('Time after prod.', help="Time in minutes for the cleaning.") routing_line_ids = fields.One2many('mrp.routing.workcenter', 'workcenter_id', "Routing Lines") order_ids = fields.One2many('mrp.workorder', 'workcenter_id', "Orders") workorder_count = fields.Integer('# Work Orders', compute='_compute_workorder_count') workorder_ready_count = fields.Integer('# Read Work Orders', compute='_compute_workorder_count') workorder_progress_count = fields.Integer( 'Total Running Orders', compute='_compute_workorder_count') workorder_pending_count = fields.Integer( 'Total Pending Orders', compute='_compute_workorder_count') workorder_late_count = fields.Integer('Total Late Orders', compute='_compute_workorder_count') time_ids = fields.One2many('mrp.workcenter.productivity', 'workcenter_id', 'Time Logs') working_state = fields.Selection([('normal', 'Normal'), ('blocked', 'Blocked'), ('done', 'In Progress')], 'Workcenter Status', compute="_compute_working_state", store=True) blocked_time = fields.Float('Blocked Time', compute='_compute_blocked_time', help='Blocked hour(s) over the last month', digits=(16, 2)) productive_time = fields.Float( 'Productive Time', compute='_compute_productive_time', help='Productive hour(s) over the last month', digits=(16, 2)) oee = fields.Float( compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month') oee_target = fields.Float(string='OEE Target', help="OEE Target in percentage", default=90) performance = fields.Integer('Performance', compute='_compute_performance', help='Performance over the last month') workcenter_load = fields.Float('Work Center Load', compute='_compute_workorder_count') @api.depends('order_ids.duration_expected', 'order_ids.workcenter_id', 'order_ids.state', 'order_ids.date_planned_start') def _compute_workorder_count(self): MrpWorkorder = self.env['mrp.workorder'] result = {wid: {} for wid in self.ids} result_duration_expected = {wid: 0 for wid in self.ids} #Count Late Workorder data = MrpWorkorder.read_group( [('workcenter_id', 'in', self.ids), ('state', 'in', ('pending', 'ready')), ('date_planned_start', '<', datetime.datetime.now().strftime('%Y-%m-%d'))], ['workcenter_id'], ['workcenter_id']) count_data = dict( (item['workcenter_id'][0], item['workcenter_id_count']) for item in data) #Count All, Pending, Ready, Progress Workorder res = MrpWorkorder.read_group( [('workcenter_id', 'in', self.ids)], ['workcenter_id', 'state', 'duration_expected'], ['workcenter_id', 'state'], lazy=False) for res_group in res: result[res_group['workcenter_id'][0]][ res_group['state']] = res_group['__count'] if res_group['state'] in ('pending', 'ready', 'progress'): result_duration_expected[res_group['workcenter_id'] [0]] += res_group['duration_expected'] for workcenter in self: workcenter.workorder_count = sum( count for state, count in result[workcenter.id].items() if state not in ('done', 'cancel')) workcenter.workorder_pending_count = result[workcenter.id].get( 'pending', 0) workcenter.workcenter_load = result_duration_expected[ workcenter.id] workcenter.workorder_ready_count = result[workcenter.id].get( 'ready', 0) workcenter.workorder_progress_count = result[workcenter.id].get( 'progress', 0) workcenter.workorder_late_count = count_data.get(workcenter.id, 0) @api.multi @api.depends('time_ids', 'time_ids.date_end', 'time_ids.loss_type') def _compute_working_state(self): for workcenter in self: # We search for a productivity line associated to this workcenter having no `date_end`. # If we do not find one, the workcenter is not currently being used. If we find one, according # to its `type_loss`, the workcenter is either being used or blocked. time_log = self.env['mrp.workcenter.productivity'].search( [('workcenter_id', '=', workcenter.id), ('date_end', '=', False)], limit=1) if not time_log: # the workcenter is not being used workcenter.working_state = 'normal' elif time_log.loss_type in ('productive', 'performance'): # the productivity line has a `loss_type` that means the workcenter is being used workcenter.working_state = 'done' else: # the workcenter is blocked workcenter.working_state = 'blocked' @api.multi def _compute_blocked_time(self): # TDE FIXME: productivity loss type should be only losses, probably count other time logs differently ?? data = self.env['mrp.workcenter.productivity'].read_group([ ('date_start', '>=', fields.Datetime.to_string(datetime.datetime.now() - relativedelta.relativedelta(months=1))), ('workcenter_id', 'in', self.ids), ('date_end', '!=', False), ('loss_type', '!=', 'productive') ], ['duration', 'workcenter_id'], ['workcenter_id'], lazy=False) count_data = dict( (item['workcenter_id'][0], item['duration']) for item in data) for workcenter in self: workcenter.blocked_time = count_data.get(workcenter.id, 0.0) / 60.0 @api.multi def _compute_productive_time(self): # TDE FIXME: productivity loss type should be only losses, probably count other time logs differently data = self.env['mrp.workcenter.productivity'].read_group([ ('date_start', '>=', fields.Datetime.to_string(datetime.datetime.now() - relativedelta.relativedelta(months=1))), ('workcenter_id', 'in', self.ids), ('date_end', '!=', False), ('loss_type', '=', 'productive') ], ['duration', 'workcenter_id'], ['workcenter_id'], lazy=False) count_data = dict( (item['workcenter_id'][0], item['duration']) for item in data) for workcenter in self: workcenter.productive_time = count_data.get(workcenter.id, 0.0) / 60.0 @api.depends('blocked_time', 'productive_time') def _compute_oee(self): for order in self: if order.productive_time: order.oee = round( order.productive_time * 100.0 / (order.productive_time + order.blocked_time), 2) else: order.oee = 0.0 @api.multi def _compute_performance(self): wo_data = self.env['mrp.workorder'].read_group([ ('date_start', '>=', fields.Datetime.to_string(datetime.datetime.now() - relativedelta.relativedelta(months=1))), ('workcenter_id', 'in', self.ids), ('state', '=', 'done') ], ['duration_expected', 'workcenter_id', 'duration'], ['workcenter_id'], lazy=False) duration_expected = dict( (data['workcenter_id'][0], data['duration_expected']) for data in wo_data) duration = dict( (data['workcenter_id'][0], data['duration']) for data in wo_data) for workcenter in self: if duration.get(workcenter.id): workcenter.performance = 100 * duration_expected.get( workcenter.id, 0.0) / duration[workcenter.id] else: workcenter.performance = 0.0 @api.multi @api.constrains('capacity') def _check_capacity(self): if any(workcenter.capacity <= 0.0 for workcenter in self): raise exceptions.UserError( _('The capacity must be strictly positive.')) @api.multi def unblock(self): self.ensure_one() if self.working_state != 'blocked': raise exceptions.UserError(_("It has been unblocked already. ")) times = self.env['mrp.workcenter.productivity'].search([ ('workcenter_id', '=', self.id), ('date_end', '=', False) ]) times.write({'date_end': fields.Datetime.now()}) return {'type': 'ir.actions.client', 'tag': 'reload'}
class Deduction(models.Model): _name = 'deduction.deduction' _inherit = 'mail.thread' name = fields.Char(string='Reference', required=True) deduction_no = fields.Char(string='Sequence', readonly=True) date = fields.Date(string="Date", default=date.today()) employee = fields.Many2one('hr.employee', string="Employee", required=False) deduction_type = fields.Many2one('deduction.type', string='Deduction Type', required=True) fix_formula = fields.Selection([('formula', 'Formula'), ("fix", "Fixed")], string='Calculation Type', required=True, default='fix') amount = fields.Float(string="Amount") description = fields.Text(string='Description', required=True) state = fields.Selection([('draft', 'Draft'), ("confirm", "Confirm"), ('hr_manager', 'Hr'), ('done', 'Done')], default='draft', string='State', track_visibility='onchange') voucher_ref = fields.Many2one('account.voucher', string='Voucher reference', readonly=True) is_paid = fields.Boolean() formual = fields.Text(placeholder="result=(wage/30)*1") @api.multi def unlink(self): for rec in self: if rec.state != 'draft': raise UserError( _('You cannot delete Form which is not is draft.')) return super(Deduction, self).unlink() @api.multi def action_confirm(self): for rec in self: if rec.amount <= 0: if rec.fix_formula != 'fix': raise ValidationError( "Please click on calc amount button, and amount must be greater than zero" ) raise ValidationError(_("Amount Must Be Greater Than Zero")) if rec.state == 'draft': rec.state = 'confirm' # if rec.deduction_type.pay_included == 'voucher': # self.generate_voucher() # else: # raise UserError("In order to confirm this deduction state must be in draft") @api.one def generate_voucher(self): voucher_obj = self.env['account.voucher'] payable_account_id = self._default_payable_account_id() expense_account_id = self._default_expense_account_id() journal_id = self._default_journal_id() if not payable_account_id: raise ValidationError( "Please check configuration for default voucher payable account" ) if not expense_account_id: raise ValidationError( "Please check configuration for default voucher expense account" ) if not journal_id: raise ValidationError( "Please check configuration for default voucher journal") if self.deduction_type.pay_included == 'voucher': employee_default_account_id = self.employee.address_home_id and self.employee.address_home_id.property_account_payable_id voucher_id = voucher_obj.create({ 'journal_id': journal_id.id, 'voucher_type': 'purchase', 'pay_now': 'pay_later', 'state': 'draft', 'account_id': employee_default_account_id.id or payable_account_id.id, 'line_ids': [(0, 0, { 'name': self.description or '/', 'quantity': 1, 'price_unit': self.amount, 'account_id': expense_account_id.id })] }) voucher_id.proforma_voucher() self.write({'voucher_ref': voucher_id.id}) @api.model def _default_payable_account_id(self): account_id = self.env['ir.config_parameter'].sudo().get_param( 'penalties_custom_12.default_voucher_payable_account_id') return self.env['account.account'].browse(int(account_id)) @api.model def _default_expense_account_id(self): account_id = self.env['ir.config_parameter'].sudo().get_param( 'penalties_custom_12.default_voucher_expense_account_id') return self.env['account.account'].browse(int(account_id)) @api.model def _default_journal_id(self): journal_id = self.env['ir.config_parameter'].sudo().get_param( 'penalties_custom_12.default_journal_id') return self.env['account.journal'].browse(int(journal_id)) @api.multi def action_hr_approval(self): for rec in self: if rec.state == 'confirm': rec.state = 'hr_manager' else: raise (_( "In order to approve this deduction state must be in confirm" )) def set_amount(self): for rec in self: amount = self.calc_amount(employee_id=rec.employee, contract=rec.employee.contract_id, rec=self).get('result', False) rec.amount = amount @api.onchange('fix_formula') def set_formula(self): if not self.formual: self.formual = self.deduction_type.formual @api.model def _default_calc_formula(self): calc_formula = self.env['ir.config_parameter'].sudo().get_param( 'penalties_custom_12.calc_formula') return self.formual def calc_amount(self, employee_id, contract, rec): employee = self.env['hr.employee'].browse(employee_id) formula_data = self._default_calc_formula() if not formula_data: raise ValidationError( "Please Check payroll Configuration. Set formula") localdict = { "employee_id": employee, "wage": contract.wage, "deduction": self, "contract": contract, 'result': 0 } safe_eval(formula_data, localdict, mode='exec', nocopy=True) return localdict @api.multi def action_ceo_approve(self): for rec in self: if rec.state == 'hr_manager': rec.state = 'done' else: raise (_( "In order to ceo approve this deduction state must be in hr approve" ))
class HrPayslip(models.Model): _inherit = 'hr.payslip' age = fields.Float('Age', compute='_compute_age_service') service_period = fields.Float('Service Period', compute='_compute_age_service') work = fields.Float('Working Period', compute='_compute_age_service') zw_idara = fields.Many2one(related='employee_id.zw_idara', string='Location', store=True) is_refund = fields.Boolean(string="Refund") employee_code = fields.Char('Employee ID', related='employee_id.employee_id', store=True) @api.multi def refund_sheet(self): for payslip in self: if payslip.is_refund: raise UserError(_('You Cannot Refund Payslip More one time.')) copied_payslip = payslip.copy( {'credit_note': True, 'is_refund': True, 'name': _('Refund: ') + payslip.name}) payslip.update({'is_refund': True}) copied_payslip.input_line_ids = payslip.input_line_ids copied_payslip.compute_sheet() copied_payslip.action_payslip_done() formview_ref = self.env.ref('hr_payroll.view_hr_payslip_form', False) treeview_ref = self.env.ref('hr_payroll.view_hr_payslip_tree', False) return { 'name': ("Refund Payslip"), 'view_mode': 'tree, form', 'view_id': False, 'view_type': 'form', 'res_model': 'hr.payslip', 'type': 'ir.actions.act_window', 'target': 'current', 'domain': "[('id', 'in', %s)]" % copied_payslip.ids, 'views': [(treeview_ref and treeview_ref.id or False, 'tree'), (formview_ref and formview_ref.id or False, 'form')], 'context': {} } @api.model def create(self, values): res = super(HrPayslip, self).create(values) payrolls = self.search([('employee_id', '=', res.employee_id.id)]).filtered(lambda pay: not pay.is_refund) for payroll in payrolls: if payroll.id != res.id and not res.is_refund: if (payroll.date_to >= res.date_from >= payroll.date_from) or ( payroll.date_to >= res.date_to >= payroll.date_from): raise UserError(_('You Cannot Create Two Payslips for one Employee In Same Period.')) return res @api.one def _compute_age_service(self): if self.employee_id.birthday: today = fields.date.today() born = datetime.strptime(str(self.employee_id.birthday), '%Y-%m-%d') self.age = today.year - born.year - ((today.month, today.day) < (born.month, born.day)) if self.employee_id.joining_date: today = fields.date.today() join_date = datetime.strptime(str(self.employee_id.joining_date), '%Y-%m-%d') diff = today - join_date.date() self.service_period = round((diff.days / 365) * 12, 2) if self.date_from and self.date_to: from_date = fields.Datetime.from_string(self.date_from) to_date = fields.Datetime.from_string(self.date_to) self.work = (to_date - from_date).days + 1 @api.onchange('employee_id', 'date_from', 'date_to') def onchange_employee(self): self.contract_id = False res = super(HrPayslip, self).onchange_employee() if self.employee_id.birthday: today = fields.date.today() born = datetime.strptime(str(self.employee_id.birthday), '%Y-%m-%d') self.age = today.year - born.year - ((today.month, today.day) < (born.month, born.day)) if self.employee_id.joining_date: today = fields.date.today() join_date = datetime.strptime(str(self.employee_id.joining_date), '%Y-%m-%d') diff = today - join_date.date() self.service_period = round((diff.days / 365) * 12, 2) if self.date_from: date_from = fields.Datetime.from_string(self.date_from) self.date_to = str(date_from + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10] return res @api.model def get_worked_day_lines(self, contract_ids, date_from, date_to): res = super(HrPayslip, self).get_worked_day_lines(contract_ids, date_from, date_to) for contract in contract_ids: overtime = { 'name': 'Number of Hours Overtime', 'sequence': 11, 'code': 'Overtime', 'number_of_days': 0.0, 'number_of_hours': 0.0, 'contract_id': contract.id, } absence = { 'name': 'Number of Days Absence', 'sequence': 12, 'code': 'Absence', 'number_of_days': 0.0, 'number_of_hours': 0.0, 'contract_id': contract.id, } unpaid = { 'name': 'Number of Days UnPaid', 'sequence': 13, 'code': 'UnPaid', 'number_of_days': 0.0, 'number_of_hours': 0.0, 'contract_id': contract.id, } late = { 'name': 'Number of Hours Late', 'sequence': 14, 'code': 'Late', 'number_of_days': 0.0, 'number_of_hours': 0.0, 'contract_id': contract.id, } res.append(overtime) res.append(absence) res.append(unpaid) res.append(late) return res
class SaleOrderLine(models.Model): _inherit = 'sale.order.line' # We compute as sudo consultant does not have access to invoices which is required when # he logs time from a task qty_invoiced = fields.Float(compute_sudo=True) vcls_type = fields.Selection( related='product_id.vcls_type', string="Vcls type", ) # Override the default ordered quantity to be 0 when we order rates items product_uom_qty = fields.Float( default=0, ) section_line_id = fields.Many2one( 'sale.order.line', string='Line section', compute='_get_section_line_id', store=False ) @api.multi def _get_section_line_id(self): for line in self: order_line_ids = line.order_id.order_line current_section_line_id = False for order_line_id in order_line_ids: if order_line_id.display_type == 'line_section': current_section_line_id = order_line_id elif line == order_line_id: line.section_line_id = current_section_line_id break def _timesheet_create_project(self): project = super(SaleOrderLine, self)._timesheet_create_project() project.update({'project_type': 'client'}) return project # We override the line creation in order to link them with existing project @api.model_create_multi def create(self, vals_list): if vals_list[0].get('order_id',False): order = self.env['sale.order'].browse(vals_list[0]['order_id']) self = self.with_context(force_company=order.company_id.id) lines = super(SaleOrderLine, self).create(vals_list) for line in lines: if (line.product_id.service_tracking in ['project_only', 'task_new_project']) and not line.product_id.project_template_id: line.project_id = line.order_id.project_id return lines @api.multi def unlink(self): for order_line in self: related_timesheet = self.env['account.analytic.line'].sudo().search([ ('so_line', '=', order_line.id), ], limit=1) if related_timesheet: raise ValidationError( _('You can not delete the "{}" order line has linked timesheets.'.format(order_line.name)) ) related_mapping = self.env['project.sale.line.employee.map'].sudo().search([ ('sale_line_id', '=', order_line.id), ]) # delete mapping linked forecast rate_employee = related_mapping.mapped('employee_id') # forecast deletion # in case this line is a service: delete forecast related to service line forecast_domain = [ ('order_line_id', '=', order_line.id), ] # Now in case this line is a rate , we search for all forecasts # having the the same rate employee (employee_id) using the mapping table # within the same project if rate_employee and order_line.order_id.project_id: forecast_domain = expression.OR([ forecast_domain, [ '&', ('employee_id', 'in', rate_employee.ids), ('project_id', '=', order_line.order_id.project_id.id), ] ]) related_forecasts = self.env['project.forecast'].sudo().search(forecast_domain) if related_forecasts: related_forecasts.sudo().unlink() # delete mapping if related_mapping: related_mapping.sudo().unlink() if order_line.vcls_type != 'rate': related_task = self.env['project.task'].sudo().search([ '|', ('sale_line_id', '=', order_line.id), ('parent_id.sale_line_id', '=', order_line.id), ]) if related_task: related_task.write({'sale_line_id': False}) related_task.sudo().unlink() return super(SaleOrderLine, self).unlink()
class CrmTeam(models.Model): _inherit = 'crm.team' use_quotations = fields.Boolean( string='Quotations', help= "Check this box if you send quotations to your customers rather than confirming orders straight away." ) invoiced = fields.Float( compute='_compute_invoiced', string='Invoiced This Month', readonly=True, help= "Invoice revenue for the current month. This is the amount the sales " "channel has invoiced this month. It is used to compute the progression ratio " "of the current and target revenue on the kanban view.") invoiced_target = fields.Float( string='Invoicing Target', help= "Revenue target for the current month (untaxed total of confirmed invoices)." ) quotations_count = fields.Integer(compute='_compute_quotations_to_invoice', string='Number of quotations to invoice', readonly=True) quotations_amount = fields.Float(compute='_compute_quotations_to_invoice', string='Amount of quotations to invoice', readonly=True) sales_to_invoice_count = fields.Integer( compute='_compute_sales_to_invoice', string='Number of sales to invoice', readonly=True) sale_order_count = fields.Integer(compute='_compute_sale_order_count', string='# Sale Orders') def _compute_quotations_to_invoice(self): query = self.env['sale.order']._where_calc([ ('team_id', 'in', self.ids), ('state', 'in', ['draft', 'sent']), ]) self.env['sale.order']._apply_ir_rules(query, 'read') _, where_clause, where_clause_args = query.get_sql() select_query = """ SELECT team_id, count(*), sum(amount_total / CASE COALESCE(currency_rate, 0) WHEN 0 THEN 1.0 ELSE currency_rate END ) as amount_total FROM sale_order WHERE %s GROUP BY team_id """ % where_clause self.env.cr.execute(select_query, where_clause_args) quotation_data = self.env.cr.dictfetchall() teams = self.browse() for datum in quotation_data: team = self.browse(datum['team_id']) team.quotations_amount = datum['amount_total'] team.quotations_count = datum['count'] teams |= team remaining = (self - teams) remaining.quotations_amount = 0 remaining.quotations_count = 0 def _compute_sales_to_invoice(self): sale_order_data = self.env['sale.order']._read_group([ ('team_id', 'in', self.ids), ('invoice_status', '=', 'to invoice'), ], ['team_id'], ['team_id']) data_map = { datum['team_id'][0]: datum['team_id_count'] for datum in sale_order_data } for team in self: team.sales_to_invoice_count = data_map.get(team.id, 0.0) def _compute_invoiced(self): if not self: return query = ''' SELECT move.team_id AS team_id, SUM(move.amount_untaxed_signed) AS amount_untaxed_signed FROM account_move move WHERE move.move_type IN ('out_invoice', 'out_refund', 'out_receipt') AND move.payment_state IN ('in_payment', 'paid', 'reversed') AND move.state = 'posted' AND move.team_id IN %s AND move.date BETWEEN %s AND %s GROUP BY move.team_id ''' today = fields.Date.today() params = [ tuple(self.ids), fields.Date.to_string(today.replace(day=1)), fields.Date.to_string(today) ] self._cr.execute(query, params) data_map = dict((v[0], v[1]) for v in self._cr.fetchall()) for team in self: team.invoiced = data_map.get(team.id, 0.0) def _compute_sale_order_count(self): data_map = {} if self.ids: sale_order_data = self.env['sale.order']._read_group([ ('team_id', 'in', self.ids), ('state', '!=', 'cancel'), ], ['team_id'], ['team_id']) data_map = { datum['team_id'][0]: datum['team_id_count'] for datum in sale_order_data } for team in self: team.sale_order_count = data_map.get(team.id, 0) def _graph_get_model(self): if self._context.get('in_sales_app'): return 'sale.report' return super(CrmTeam, self)._graph_get_model() def _graph_date_column(self): if self._context.get('in_sales_app'): return 'date' return super(CrmTeam, self)._graph_date_column() def _graph_y_query(self): if self._context.get('in_sales_app'): return 'SUM(price_subtotal)' return super(CrmTeam, self)._graph_y_query() def _extra_sql_conditions(self): if self._context.get('in_sales_app'): return "AND state in ('sale', 'done', 'pos_done')" return super(CrmTeam, self)._extra_sql_conditions() def _graph_title_and_key(self): if self._context.get('in_sales_app'): return ['', _('Sales: Untaxed Total')] # no more title return super(CrmTeam, self)._graph_title_and_key() def _compute_dashboard_button_name(self): super(CrmTeam, self)._compute_dashboard_button_name() if self._context.get('in_sales_app'): self.update({'dashboard_button_name': _("Sales Analysis")}) def action_primary_channel_button(self): if self._context.get('in_sales_app'): return self.env["ir.actions.actions"]._for_xml_id( "sale.action_order_report_so_salesteam") return super(CrmTeam, self).action_primary_channel_button() def update_invoiced_target(self, value): return self.write({'invoiced_target': round(float(value or 0))}) @api.ondelete(at_uninstall=False) def _unlink_except_used_for_sales(self): """ If more than 5 active SOs, we consider this team to be actively used. 5 is some random guess based on "user testing", aka more than testing CRM feature and less than use it in real life use cases. """ SO_COUNT_TRIGGER = 5 for team in self: if team.sale_order_count >= SO_COUNT_TRIGGER: raise UserError( _('Team %(team_name)s has %(sale_order_count)s active sale orders. Consider canceling them or archiving the team instead.', team_name=team.name, sale_order_count=team.sale_order_count))
class HRStaffSalaryDifference(models.Model): _name = "hr.staff.salary.difference" _inherit = ['mail.thread'] _description = "HR Staff Salary Difference" @api.one @api.depends('line_ids','line_ids.amount') def _get_total_amount(self): total = 0 for line_id in self.line_ids: total += line_id.amount self.total = total name = fields.Char("Name",required=True) date = fields.Date('Date',default=lambda self: datetime.today().strftime('%Y-%m-%d'),track_visibility='onchange') segment_id = fields.Many2one('hr.segmentation','Segment', default=1,track_visibility='onchange') salary_payable_account = fields.Many2one('account.account','Salary Payable Account',required=True,readonly=True, default=141, track_visibility='onchange') expense_account = fields.Many2one('account.account','Expense Account',required=True,readonly=True,default=111,track_visibility='onchange') journal_id = fields.Many2one('account.journal','Journal', default=41,track_visibility='onchange') move_id = fields.Many2one('account.move','Accounting Entry',readonly=True) state = fields.Selection([('draft','Draft'),('validate','Validate'),('paid','Paid')],'Status',default='draft', track_visibility='onchange') total = fields.Float(compute='_get_total_amount',string='Total', required=False,store=True) line_ids = fields.One2many('hr.staff.salary.difference.line', 'difference_id', 'Lines') date_from = fields.Date('Date From', required=True,default=lambda *a: str(datetime.now() + relativedelta.relativedelta(day=1))[:10]) date_to = fields.Date('Date To', required=True,default=lambda *a: str(datetime.now() + relativedelta.relativedelta(day=31))[:10]) note = fields.Text('Note') @api.one def unlink(self): if self.state != 'draft': raise UserError(('You can not delete Record which are not in Draft State. Please Shift First in Draft state then delete it.')) ret = super(hr_staff_salary_difference, self).unlink() return ret @api.one def salary_validate(self): self.state = 'validate' @api.one def salary_paid(self): move_obj = self.env['account.move'] move_lines = [] timenow = time.strftime('%Y-%m-%d') model_rec = self.env['ir.model'].search([('model','=','hr.staff.salary.difference')]) auto_entries = self.env['auto.dimensions.entry'].search([('model_id','=',model_rec.id),('active','=',True)],order='sequence') name = self.name move = { 'narration': self.name, 'date': self.date or timenow, 'journal_id': self.journal_id.id, } for rec in self.line_ids: debit_line = (0, 0, { 'name': (_('%s of %s:%s') %(self.name, rec.employee_id.code,rec.employee_id.name)), 'date': self.date or timenow, 'account_id': self.expense_account.id, 'journal_id': self.journal_id.id, 'debit': rec.amount, 'credit': 0.0, }) number = 0 nd_ids = self.expense_account.nd_ids if nd_ids: for auto_entry in auto_entries: if auto_entry.dimension_id in nd_ids: debit_line[2].update({auto_entry.dst_col.name : eval(auto_entry.src_fnc)}) ans = self.env['analytic.structure'].search([('model_name','=','account_move_line'),('nd_id','=',auto_entry.dimension_id.id)]) number += math.pow(2,int(ans.ordering)-1) debit_line[2].update({'d_bin' : bin(int(number))[2:].zfill(10)}) move_lines.append(debit_line) credit_line = (0, 0, { 'name': (_('%s of %s:%s') %(self.name, rec.employee_id.code,rec.employee_id.name)), 'date': self.date or timenow, 'journal_id': self.journal_id.id, 'account_id': self.salary_payable_account.id, 'debit': 0.0, 'credit': rec.amount, }) number = 0 nd_ids = self.salary_payable_account.nd_ids if nd_ids: for auto_entry in auto_entries: if auto_entry.dimension_id in nd_ids: credit_line[2].update({auto_entry.dst_col.name : eval(auto_entry.src_fnc)}) ans = self.env['analytic.structure'].search([('model_name','=','account_move_line'),('nd_id','=',auto_entry.dimension_id.id)]) number += math.pow(2,int(ans.ordering)-1) credit_line[2].update({'d_bin' : bin(int(number))[2:].zfill(10)}) move_lines.append(credit_line) move.update({'line_ids': move_lines}) move = move_obj.create(move) self.write({'move_id': move.id, 'state': 'paid'}) move.post()
class RequestWizardStopWork(models.TransientModel): _name = 'request.wizard.stop.work' _description = 'Request Wizard: Stop Work' timesheet_line_id = fields.Many2one( 'request.timesheet.line', required=True) date_start = fields.Datetime( related='timesheet_line_id.date_start', readonly=True) request_id = fields.Many2one( 'request.request', related='timesheet_line_id.request_id', readonly=True) request_type_id = fields.Many2one( 'request.type', related='timesheet_line_id.request_id.type_id', readonly=True, string="Request Type") request_text_sample = fields.Text( related='timesheet_line_id.request_id.request_text_sample', readonly=True) activity_id = fields.Many2one( 'request.timesheet.activity', required=True, ondelete='cascade') amount = fields.Float(required=True) description = fields.Text() @api.model def default_get(self, fields_list): res = super(RequestWizardStopWork, self).default_get(fields_list) if res.get('timesheet_line_id') and 'amount' in fields_list: line = self.env['request.timesheet.line'].browse( res['timesheet_line_id']) start = fields.Datetime.from_string(line.date_start) end = fields.Datetime.from_string(fields.Datetime.now()) amount_seconds = (end - start).total_seconds() if amount_seconds <= 60: # Ensure minimal amount is 1 minute amount_seconds = 61 res.update({ 'amount': amount_seconds / 3600, }) return res def _prepare_timesheet_line_data(self): return { 'amount': self.amount, 'activity_id': self.activity_id.id, 'description': self.description, } def do_stop_work(self): self.ensure_one() self.timesheet_line_id.write( self._prepare_timesheet_line_data() ) self.request_id.trigger_event('timetracking-stop-work', { 'timesheet_line_id': self.timesheet_line_id.id, }) if self.env.context.get('request_timesheet_start_request_id'): self.env['request.request'].browse( self.env.context.get('request_timesheet_start_request_id') ).action_start_work()
class StockMove(models.Model): _inherit = 'stock.move' kardex_price_unit = fields.Float(string='Kardex Price Unit', default=0) # digits=(total, decimal) type_operation_sunat_id = fields.Many2one('type.operation.kardex', 'Tipo de Transacción') valoracion = fields.One2many(comodel_name='stock.valuation.layer', inverse_name='stock_move_id', string='Valoracion') def set_kardex_price_unit(self): total = 0 for item in self.valoracion: total += item.value self.kardex_price_unit = total / (self.product_qty + 0.0000000000000000000000000001) self.kardex_price_unit = self.kardex_price_unit - self.price_unit def _create_in_svl(self, forced_quantity=None): res = super(StockMove, self)._create_in_svl() for valuation in res: # Si existen ajustes de inventario inicial if valuation.stock_move_id.inventory_id: for line in valuation.stock_move_id.inventory_id.line_ids: if line.product_id == valuation.product_id and line.location_id == valuation.stock_move_id.location_dest_id and line.is_initial: valuation.update({ 'unit_cost': line.initial_cost, 'value': line.initial_cost * valuation.quantity, 'remaining_value': line.initial_cost * valuation.quantity }) line.product_id.standard_price = line.initial_cost else: if valuation.stock_move_id.purchase_line_id: price_unit = valuation.stock_move_id.purchase_line_id.price_unit else: if valuation.stock_move_id.origin_returned_move_id: vl = self.env['stock.valuation.layer'].search([ ('stock_move_id', '=', valuation.stock_move_id.origin_returned_move_id.id ), ('product_id', '=', valuation.stock_move_id.product_id.id) ]) price_unit = vl.unit_cost else: # TODO pensar como hacer para mover productos de una almacen a otro # que pueden tener costo valorizados distintos price_unit = valuation.stock_move_id.product_id.standard_price if valuation.stock_move_id.purchase_line_id.currency_id and valuation.stock_move_id.purchase_line_id.currency_id != self.env.user.company_id.currency_id: rate = valuation.currency_id._get_conversion_rate( valuation.stock_move_id.purchase_line_id.currency_id, self.env.user.company_id.currency_id, self.env.user.company_id, valuation.stock_move_id.picking_id.kardex_date if valuation.stock_move_id.picking_id.use_kardex_date else valuation.stock_move_id.picking_id.scheduled_date) if valuation.stock_move_id.picking_id.invoice_id: # or valuation.stock_move_id.picking_id.invoice_id_purchase: # if valuation.stock_move_id.picking_id.invoice_id: if valuation.stock_move_id.picking_id.invoice_id.tc_per: price_unit = price_unit * valuation.stock_move_id.picking_id.invoice_id.currency_rate else: price_unit = price_unit * rate else: price_unit = price_unit * rate valuation.update({ 'unit_cost': price_unit, 'value': price_unit * valuation.quantity, 'remaining_value': price_unit * valuation.quantity }) return res
class ProductKardexLine(models.TransientModel): _name = "product.product.kardex.line" template = fields.Many2one(comodel_name='product.template', string='template') name = fields.Many2one(comodel_name='product.product', string='Producto') type_operation_sunat_id = fields.Many2one('type.operation.kardex', 'Tipo de Transacción') fecha = fields.Date(string='Fecha') cantidad_inicial = fields.Float(string='Cantidad Incial') costo_intradas = fields.Float(string='Costo de Inicial') total_bolivares_inicial = fields.Float(string='Total Bolivares Inicial') category_id = fields.Many2one(comodel_name='product.category', string='Categoria') cantidad_entradas = fields.Float(string='Cantidad Entradas') costo_entradas = fields.Float(string='Costo de Entradas') total_bolivares_entradas = fields.Float(string='Total Bolivares ') cantidad_salidas = fields.Float(string='Cantidad Salidas') costo_salidas = fields.Float(string='Costo de Salidas') total_bolivares_salida = fields.Float(string='Total Bolivares') total = fields.Float(string='Total') promedio = fields.Float(string='Promedio') total_bolivares = fields.Float(string='Total Bolivares')
class ResCurrency(models.Model): _inherit = 'res.currency.rate' rate = fields.Float(digits=dp.get_precision('Rate Precision'))
class MakingCharges(models.Model): _name = 'making.charges' _description = 'Making Charges' name = fields.Char('Name') making_charge = fields.Float('Charges')
class ResCompanyProperty(models.Model): """ We dont use res.company because most user dont' have access rights to edit it """ _name = "res.company.property" _description = "Company Property" _auto = False # _rec_name = 'display_name' _depends = { 'res.company': [ 'id', ], } company_id = fields.Many2one( 'res.company', 'Company', required=True, ) @api.model_cr def init(self): cr = self._cr tools.drop_view_if_exists(cr, self._table) query = """ SELECT c.id as id, c.id as company_id FROM res_company c """ cr.execute("""CREATE or REPLACE VIEW %s as (%s )""" % (self._table, query)) property_field = fields.Char(compute='_compute_property_field', ) property_account_id = fields.Many2one( 'account.account', string='Account', compute='_compute_property_account', inverse='_inverse_property_account', ) property_term_id = fields.Many2one( 'account.payment.term', string='Payment Term', compute='_compute_property_term', inverse='_inverse_property_term', ) property_position_id = fields.Many2one( 'account.fiscal.position', string='Fiscal Position', compute='_compute_property_position', inverse='_inverse_property_position', ) standard_price = fields.Float( string="standard_price", compute='_compute_property_standard_price', inverse='_inverse_property_standard_price', digits=dp.get_precision('Product Price'), ) # display_name = fields.Char( # compute='_compute_display_name' # ) @api.model def _get_companies(self): domain = [] comodel = self._get_property_comodel() if comodel in ['account.account']: domain = [('company_id.consolidation_company', '=', False)] return self.search(domain) @api.model def action_company_properties(self): property_field = self._context.get('property_field', False) if not property_field: return True company_properties = self._get_companies() action = self.env.ref( 'account_multicompany_ux.action_res_company_property') if not action: return False action_read = action.read()[0] # do this because raise an error if came from a view # with group_by activated ctx = self._context.copy() ctx.pop('group_by', None) action_read['context'] = ctx action_read['domain'] = [('id', 'in', company_properties.ids)] return action_read @api.model def _get_property_comodel(self): property_field = self._context.get('property_field') record = self._get_record() if record: field = self._get_record()._fields.get(property_field) return field and field.comodel_name or False @api.model def _get_record(self): context = self._context active_model = context.get('active_model') active_id = context.get('active_id') property_field = context.get('property_field') if not property_field or not active_id or not active_model: _logger.warn('Could not get property record from context %s' % context) return False return self.with_context( force_company=self.id).env[active_model].browse(active_id) @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): """ Con esta funcion hacemos dos cosas: 1. Mostrar solo la columna que corresponda segun el modelo 2. Agregar dominio segun el dominio original de la porperty mas de cia """ res = super(ResCompanyProperty, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) doc = etree.XML(res['arch']) property_field = self._context.get('property_field') domain = self._context.get('property_domain') record = self._get_record() if record: field = self._get_record()._fields.get(property_field) # si no viene definido un property_domain buscamos uno para # definido en el campo de la property if not domain: try: domain = literal_eval(field.domain) except: domain = [] domain_elements = [str(x) for x in domain] # add company domain if comodel has company comodel = self._get_property_comodel() if comodel: if self.env[comodel]._fields.get('company_id'): domain_elements += [ "'|'", "('company_id', '=', False)", "('company_id', '=', company_id)" ] str_domain = '[%s]' % ','.join(domain_elements) company_property_field = self._get_company_property_field() xpath = "//field[@name='%s']" % company_property_field for node in doc.xpath(xpath): node.set('domain', str(str_domain)) node.set('invisible', '0') # modifiers make still invisible = 1 setup_modifiers(node, res['fields'][company_property_field]) res['arch'] = etree.tostring(doc) return res @api.model def _get_company_property_field(self): comodel = self._get_property_comodel() if comodel == 'account.account': company_property_field = 'property_account_id' elif comodel == 'account.fiscal.position': company_property_field = 'property_position_id' elif comodel == 'account.payment.term': company_property_field = 'property_term_id' else: property_field = self._context.get('property_field') if property_field == 'standard_price': company_property_field = 'standard_price' else: raise Warning( _('Property for model %s not implemented yet' % comodel)) return company_property_field @api.multi def name_get(self): """ No llamamos a super porque tendriamos que igualmente hacer un read para obtener la compania y no queremos disminuir la performance """ # por ahora en campos calculados no podemos cambiar el contexto de esta # manera # for rec in self.with_context(no_company_sufix=True): res = [] for rec in self: company_field = getattr(rec.with_context(no_company_sufix=True), rec._get_company_property_field()) if type(company_field) is float: precision_digits = self.env['decimal.precision'].precision_get( 'Product Price') # por alguna razon el float_round no nos está funcionando # y usamos round directamente (por ej. para valor 42,66) company_field = company_field and round( company_field, precision_digits) # company_field = company_field and float_round( # company_field, precision_digits=precision_digits) display_name = '%s%s' % (company_field or _('None'), rec.company_id.get_company_sufix()) else: display_name = '%s%s' % (company_field.display_name or _('None'), rec.company_id.get_company_sufix()) res.append((rec.id, display_name)) return res def _compute_property_field(self): for record in self: record.property_field = self._context.get('property_field', '') @api.multi def _get_property_value(self): self.ensure_one() property_field = self.property_field record = self._get_record() if not record or not property_field: return False return getattr(record, property_field) def _compute_property_standard_price(self): for record in self.filtered( lambda x: x.property_field == 'standard_price'): record.standard_price = record._get_property_value() def _compute_property_account(self): for record in self: if record._get_property_comodel() == 'account.account': record.property_account_id = record._get_property_value() def _compute_property_position(self): for record in self: if record._get_property_comodel() == 'account.fiscal.position': record.property_position_id = record._get_property_value() def _compute_property_term(self): for record in self: if record._get_property_comodel() == 'account.payment.term': record.property_term_id = record._get_property_value() @api.multi def _set_property_value(self, value): self.ensure_one() record = self._get_record() property_field = self.property_field if not record or not property_field: return True setattr(record, property_field, value) @api.multi def _inverse_property_account(self): for rec in self: rec._set_property_value(rec.property_account_id.id) @api.multi def _inverse_property_position(self): for rec in self: rec._set_property_value(rec.property_position_id.id) @api.multi def _inverse_property_term(self): for rec in self: rec._set_property_value(rec.property_term_id.id) @api.multi def _inverse_property_standard_price(self): for rec in self: rec._set_property_value(rec.standard_price)
class TSTInheritPosOrder(models.Model): _inherit = "pos.order" employees_ids = fields.One2many("tst.table.employees","pos_order_id", string="Working Employees") car_id = fields.Many2one("user.cars", string="Selected Car") per_day_reading = fields.Float("Car Reading Per Days") current_reading = fields.Float("Current Reading") next_oil_change_km = fields.Float("Next Oil Change KM") next_oil_change_date = fields.Date("Next Oil Change Date") car_per_day_read_expect = fields.Integer("Expected Car Reading After KM") order_id = fields.Many2one("pos.order", string="POS Order") @api.model def create(self, values): if values.get('session_id'): # set name based on the sequence specified on the config session = self.env['pos.session'].browse(values['session_id']) values['name'] = session.config_id.sequence_id._next() values.setdefault('pricelist_id', session.config_id.pricelist_id.id) else: # fallback on any pos.order sequence values['name'] = self.env['ir.sequence'].next_by_code('pos.order') getCreate = super(TSTInheritPosOrder, self).create(values) reading = self.env['user.cars.readings'].browse(values['reading_id']) reading.pos_order_id = getCreate.id return getCreate @api.multi def get_pos_order(self, vals): html = "" if 'order_id' in vals: html = "<tr class='readings-order-table-row'><td colspan='6'><table class='sub-table-table' style='width:100%'>" html += "<tr><th class='text-center'>Product Name</th><th class='text-center'>Quantity</th></tr>" pos_order = self.env['pos.order'].browse(int(vals['order_id'])) if pos_order: for line in pos_order.lines: html += "<tr><td class='text-center'>"+ line.product_id.name +"</td><td class='text-center'>"+ str(line.qty) +"</td></tr>" html += "</table><tr><td>" return html if html == '': return False @api.model def _order_fields(self, ui_order): process_line = partial(self.env['pos.order.line']._order_line_fields) terms = [] values = {} values['per_day_reading'] = ui_order.get('per_day_reading') or False values['current_reading'] = ui_order.get('current_reading') or False values['next_oil_change_km'] = ui_order.get('next_oil_change') or False values['next_oil_change_date'] = ui_order.get('next_oil_change_date') or False values['car_per_day_read_expect'] = ui_order.get('car_per_day_read_expect') or False values['car_id'] = ui_order.get('selected_car') or False values['pos_order_id'] = False reading_id = None if 'selected_employees' in ui_order: for emp in ui_order['selected_employees']: vals = {} vals['emp_id'] = emp terms.append((0, 0, vals)) try: if values.get('car_id'): if values.get('next_oil_change_date'): try: values['next_oil_change_date'] = parser.parse(values.get('next_oil_change_date')) values['next_oil_change_date'] = values['next_oil_change_date'].date() values['next_oil_change_date'] = str(values['next_oil_change_date']) except: pass reading_id = self.env['user.cars.readings'].create(values) cars_model = self.env['user.cars'] res = cars_model.search([('id', '=', values['car_id'])]).read() if len(res): res = res[0] car_values = {} field_count = 0 if not res.get('car_reading_per_day'): if values.get('per_day_reading'): car_values['car_reading_per_day'] = values['per_day_reading'] field_count += 1 if not res.get('oil_change_after_reading'): if values.get('next_oil_change_date'): car_values['oil_change_after_reading'] = values['next_oil_change_date'] field_count += 1 if field_count: cars_model.write(car_values) except Exception as e: print (str(e)) raise ValidationError(e) res = { 'name': ui_order['name'], 'user_id': ui_order['user_id'] or False, 'session_id': ui_order['pos_session_id'], 'lines': [process_line(l) for l in ui_order['lines']] if ui_order['lines'] else False, 'pos_reference': ui_order['name'], 'partner_id': ui_order['partner_id'] or False, 'date_order': ui_order['creation_date'], 'fiscal_position_id': ui_order['fiscal_position_id'], 'employees_ids': terms, 'amount_tax': ui_order.get('amount_tax') or False, 'amount_total': ui_order.get('amount_total') or False, 'amount_paid': ui_order.get('amount_paid') or False, 'amount_return': ui_order.get('amount_return') or False, 'car_id': values['car_id'], 'per_day_reading': values['per_day_reading'], 'current_reading': values['current_reading'], 'car_per_day_read_expect': values['car_per_day_read_expect'], 'next_oil_change_km': values['next_oil_change_km'], 'next_oil_change_date': values['next_oil_change_date'], 'reading_id': reading_id.id if reading_id else False } return res
class PricelistItem(models.Model): _name = "product.pricelist.item" _description = "Pricelist item" _order = "applied_on, min_quantity desc, categ_id desc, id" product_tmpl_id = fields.Many2one( 'product.template', 'Product Template', ondelete='cascade', help="Specify a template if this rule only applies to one product template. Keep empty otherwise.") product_id = fields.Many2one( 'product.product', 'Product', ondelete='cascade', help="Specify a product if this rule only applies to one product. Keep empty otherwise.") categ_id = fields.Many2one( 'product.category', 'Product Category', ondelete='cascade', help="Specify a product category if this rule only applies to products belonging to this category or its children categories. Keep empty otherwise.") min_quantity = fields.Integer( 'Min. Quantity', default=1, help="For the rule to apply, bought/sold quantity must be greater " "than or equal to the minimum quantity specified in this field.\n" "Expressed in the default unit of measure of the product.") applied_on = fields.Selection([ ('3_global', 'Global'), ('2_product_category', ' Product Category'), ('1_product', 'Product'), ('0_product_variant', 'Product Variant')], "Apply On", default='3_global', required=True, help='Pricelist Item applicable on selected option') sequence = fields.Integer( 'Sequence', default=5, required=True, help="Gives the order in which the pricelist items will be checked. The evaluation gives highest priority to lowest sequence and stops as soon as a matching item is found.") base = fields.Selection([ ('list_price', 'Public Price'), ('standard_price', 'Cost'), ('pricelist', 'Other Pricelist')], "Based on", default='list_price', required=True, help='Base price for computation.\n' 'Public Price: The base price will be the Sale/public Price.\n' 'Cost Price : The base price will be the cost price.\n' 'Other Pricelist : Computation of the base price based on another Pricelist.') base_pricelist_id = fields.Many2one('product.pricelist', 'Other Pricelist') pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', index=True, ondelete='cascade') price_surcharge = fields.Float( 'Price Surcharge', digits=dp.get_precision('Product Price'), help='Specify the fixed amount to add or substract(if negative) to the amount calculated with the discount.') price_discount = fields.Float('Price Discount', default=0, digits=(16, 2)) price_round = fields.Float( 'Price Rounding', digits=dp.get_precision('Product Price'), help="Sets the price so that it is a multiple of this value.\n" "Rounding is applied after the discount and before the surcharge.\n" "To have prices that end in 9.99, set rounding 10, surcharge -0.01") price_min_margin = fields.Float( 'Min. Price Margin', digits=dp.get_precision('Product Price'), help='Specify the minimum amount of margin over the base price.') price_max_margin = fields.Float( 'Max. Price Margin', digits=dp.get_precision('Product Price'), help='Specify the maximum amount of margin over the base price.') company_id = fields.Many2one( 'res.company', 'Company', readonly=True, related='pricelist_id.company_id', store=True) currency_id = fields.Many2one( 'res.currency', 'Currency', readonly=True, related='pricelist_id.currency_id', store=True) date_start = fields.Date('Start Date', help="Starting date for the pricelist item validation") date_end = fields.Date('End Date', help="Ending valid for the pricelist item validation") compute_price = fields.Selection([ ('fixed', 'Fix Price'), ('percentage', 'Percentage (discount)'), ('formula', 'Formula')], index=True, default='fixed') fixed_price = fields.Float('Fixed Price', digits=dp.get_precision('Product Price')) percent_price = fields.Float('Percentage Price') # functional fields used for usability purposes name = fields.Char( 'Name', compute='_get_pricelist_item_name_price', help="Explicit rule name for this pricelist line.") price = fields.Char( 'Price', compute='_get_pricelist_item_name_price', help="Explicit rule name for this pricelist line.") @api.constrains('base_pricelist_id', 'pricelist_id', 'base') def _check_recursion(self): if any(item.base == 'pricelist' and item.pricelist_id and item.pricelist_id == item.base_pricelist_id for item in self): raise ValidationError(_('Error! You cannot assign the Main Pricelist as Other Pricelist in PriceList Item!')) return True @api.constrains('price_min_margin', 'price_max_margin') def _check_margin(self): if any(item.price_min_margin > item.price_max_margin for item in self): raise ValidationError(_('Error! The minimum margin should be lower than the maximum margin.')) return True @api.one @api.depends('categ_id', 'product_tmpl_id', 'product_id', 'compute_price', 'fixed_price', \ 'pricelist_id', 'percent_price', 'price_discount', 'price_surcharge') def _get_pricelist_item_name_price(self): if self.categ_id: self.name = _("Category: %s") % (self.categ_id.name) elif self.product_tmpl_id: self.name = self.product_tmpl_id.name elif self.product_id: self.name = self.product_id.display_name.replace('[%s]' % self.product_id.code, '') else: self.name = _("All Products") if self.compute_price == 'fixed': self.price = ("%s %s") % (self.fixed_price, self.pricelist_id.currency_id.name) elif self.compute_price == 'percentage': self.price = _("%s %% discount") % (self.percent_price) else: self.price = _("%s %% discount and %s surcharge") % (abs(self.price_discount), self.price_surcharge) @api.onchange('applied_on') def _onchange_applied_on(self): if self.applied_on != '0_product_variant': self.product_id = False if self.applied_on != '1_product': self.product_tmpl_id = False if self.applied_on != '2_product_category': self.categ_id = False @api.onchange('compute_price') def _onchange_compute_price(self): if self.compute_price != 'fixed': self.fixed_price = 0.0 if self.compute_price != 'percentage': self.percent_price = 0.0 if self.compute_price != 'formula': self.update({ 'price_discount': 0.0, 'price_surcharge': 0.0, 'price_round': 0.0, 'price_min_margin': 0.0, 'price_max_margin': 0.0, })
class LunchOrder(models.Model): _name = 'lunch.order' _description = 'Lunch Order' _order = 'id desc' _display_name = 'product_id' name = fields.Char(related='product_id.name', string="Product Name", readonly=True) # to remove topping_ids_1 = fields.Many2many('lunch.topping', 'lunch_order_topping', 'order_id', 'topping_id', string='Extras 1', domain=[('topping_category', '=', 1)]) topping_ids_2 = fields.Many2many('lunch.topping', 'lunch_order_topping', 'order_id', 'topping_id', string='Extras 2', domain=[('topping_category', '=', 2)]) topping_ids_3 = fields.Many2many('lunch.topping', 'lunch_order_topping', 'order_id', 'topping_id', string='Extras 3', domain=[('topping_category', '=', 3)]) product_id = fields.Many2one('lunch.product', string="Product", required=True) category_id = fields.Many2one(string='Product Category', related='product_id.category_id', store=True) date = fields.Date('Order Date', required=True, readonly=True, states={'new': [('readonly', False)]}, default=fields.Date.context_today) supplier_id = fields.Many2one(string='Vendor', related='product_id.supplier_id', store=True, index=True) user_id = fields.Many2one('res.users', 'User', readonly=True, states={'new': [('readonly', False)]}, default=lambda self: self.env.uid) note = fields.Text('Notes') price = fields.Float('Total Price', compute='_compute_total_price', readonly=True, store=True, digits='Account') active = fields.Boolean('Active', default=True) state = fields.Selection([('new', 'To Order'), ('ordered', 'Ordered'), ('confirmed', 'Received'), ('cancelled', 'Cancelled')], 'Status', readonly=True, index=True, default='new') company_id = fields.Many2one('res.company', default=lambda self: self.env.company.id) currency_id = fields.Many2one(related='company_id.currency_id', store=True) quantity = fields.Float('Quantity', required=True, default=1) display_toppings = fields.Text('Extras', compute='_compute_display_toppings', store=True) product_description = fields.Text('Description', related='product_id.description') topping_label_1 = fields.Char( related='product_id.category_id.topping_label_1') topping_label_2 = fields.Char( related='product_id.category_id.topping_label_2') topping_label_3 = fields.Char( related='product_id.category_id.topping_label_3') topping_quantity_1 = fields.Selection( related='product_id.category_id.topping_quantity_1') topping_quantity_2 = fields.Selection( related='product_id.category_id.topping_quantity_2') topping_quantity_3 = fields.Selection( related='product_id.category_id.topping_quantity_3') image_1920 = fields.Image(compute='_compute_product_images') image_128 = fields.Image(compute='_compute_product_images') available_toppings_1 = fields.Boolean( help='Are extras available for this product', compute='_compute_available_toppings') available_toppings_2 = fields.Boolean( help='Are extras available for this product', compute='_compute_available_toppings') available_toppings_3 = fields.Boolean( help='Are extras available for this product', compute='_compute_available_toppings') @api.depends('product_id') def _compute_product_images(self): for line in self: line.image_1920 = line.product_id.image_1920 or line.category_id.image_1920 line.image_128 = line.product_id.image_128 or line.category_id.image_128 @api.depends('category_id') def _compute_available_toppings(self): for line in self: line.available_toppings_1 = bool( line.env['lunch.topping'].search_count([ ('category_id', '=', line.category_id.id), ('topping_category', '=', 1) ])) line.available_toppings_2 = bool( line.env['lunch.topping'].search_count([ ('category_id', '=', line.category_id.id), ('topping_category', '=', 2) ])) line.available_toppings_3 = bool( line.env['lunch.topping'].search_count([ ('category_id', '=', line.category_id.id), ('topping_category', '=', 3) ])) def init(self): self._cr.execute( """CREATE INDEX IF NOT EXISTS lunch_order_user_product_date ON %s (user_id, product_id, date)""" % self._table) def _extract_toppings(self, values): """ If called in api.multi then it will pop topping_ids_1,2,3 from values """ if self.ids: # TODO This is not taking into account all the toppings for each individual order, this is usually not a problem # since in the interface you usually don't update more than one order at a time but this is a bug nonetheless topping_1 = values.pop('topping_ids_1')[0][ 2] if 'topping_ids_1' in values else self[:1].topping_ids_1.ids topping_2 = values.pop('topping_ids_2')[0][ 2] if 'topping_ids_2' in values else self[:1].topping_ids_2.ids topping_3 = values.pop('topping_ids_3')[0][ 2] if 'topping_ids_3' in values else self[:1].topping_ids_3.ids else: topping_1 = values['topping_ids_1'][0][ 2] if 'topping_ids_1' in values else [] topping_2 = values['topping_ids_2'][0][ 2] if 'topping_ids_2' in values else [] topping_3 = values['topping_ids_3'][0][ 2] if 'topping_ids_3' in values else [] return topping_1 + topping_2 + topping_3 @api.constrains('topping_ids_1', 'topping_ids_2', 'topping_ids_3') def _check_topping_quantity(self): errors = { '1_more': _('You should order at least one %s'), '1': _('You have to order one and only one %s'), } for line in self: for index in range(1, 4): availability = line['available_toppings_%s' % index] quantity = line['topping_quantity_%s' % index] toppings = line['topping_ids_%s' % index].filtered( lambda x: x.topping_category == index) label = line['topping_label_%s' % index] if availability and quantity != '0_more': check = bool( len(toppings) == 1 if quantity == '1' else toppings) if not check: raise ValidationError(errors[quantity] % label) @api.model def create(self, values): lines = self._find_matching_lines({ **values, 'toppings': self._extract_toppings(values), }) if lines: # YTI FIXME This will update multiple lines in the case there are multiple # matching lines which should not happen through the interface lines.update_quantity(1) return lines[:1] return super().create(values) def write(self, values): merge_needed = 'note' in values or 'topping_ids_1' in values or 'topping_ids_2' in values or 'topping_ids_3' in values if merge_needed: lines_to_deactivate = self.env['lunch.order'] for line in self: # Only write on topping_ids_1 because they all share the same table # and we don't want to remove all the records # _extract_toppings will pop topping_ids_1, topping_ids_2 and topping_ids_3 from values # This also forces us to invalidate the cache for topping_ids_2 and topping_ids_3 that # could have changed through topping_ids_1 without the cache knowing about it toppings = self._extract_toppings(values) self.invalidate_cache(['topping_ids_2', 'topping_ids_3']) values['topping_ids_1'] = [(6, 0, toppings)] matching_lines = self._find_matching_lines({ 'user_id': values.get('user_id', line.user_id.id), 'product_id': values.get('product_id', line.product_id.id), 'note': values.get('note', line.note or False), 'toppings': toppings, }) if matching_lines: lines_to_deactivate |= line # YTI TODO Try to batch it, be careful there might be multiple matching # lines for the same order hence quantity should not always be # line.quantity, but rather a sum matching_lines.update_quantity(line.quantity) lines_to_deactivate.write({'active': False}) return super(LunchOrder, self - lines_to_deactivate).write(values) return super().write(values) @api.model def _find_matching_lines(self, values): domain = [ ('user_id', '=', values.get('user_id', self.default_get(['user_id'])['user_id'])), ('product_id', '=', values.get('product_id', False)), ('date', '=', fields.Date.today()), ('note', '=', values.get('note', False)), ] toppings = values.get('toppings', []) return self.search(domain).filtered( lambda line: (line.topping_ids_1 | line.topping_ids_2 | line. topping_ids_3).ids == toppings) @api.depends('topping_ids_1', 'topping_ids_2', 'topping_ids_3', 'product_id', 'quantity') def _compute_total_price(self): for line in self: line.price = line.quantity * (line.product_id.price + sum( (line.topping_ids_1 | line.topping_ids_2 | line.topping_ids_3).mapped('price'))) @api.depends('topping_ids_1', 'topping_ids_2', 'topping_ids_3') def _compute_display_toppings(self): for line in self: toppings = line.topping_ids_1 | line.topping_ids_2 | line.topping_ids_3 line.display_toppings = ' + '.join(toppings.mapped('name')) def update_quantity(self, increment): for line in self.filtered(lambda line: line.state != 'confirmed'): if line.quantity <= -increment: # TODO: maybe unlink the order? line.active = False else: line.quantity += increment self._check_wallet() def add_to_cart(self): """ This method currently does nothing, we currently need it in order to be able to reuse this model in place of a wizard """ # YTI FIXME: Find a way to drop this. return True def _check_wallet(self): self.flush() for line in self: if self.env['lunch.cashmove'].get_wallet_balance(line.user_id) < 0: raise ValidationError( _('Your wallet does not contain enough money to order that. To add some money to your wallet, please contact your lunch manager.' )) def action_order(self): if self.filtered(lambda line: not line.product_id.active): raise ValidationError(_('Product is no longer available.')) self.write({'state': 'ordered'}) self._check_wallet() def action_confirm(self): self.write({'state': 'confirmed'}) def action_cancel(self): self.write({'state': 'cancelled'})
class ProductTemplate(models.Model): _name = "product.template" _inherit = ['mail.thread'] _description = "Product Template" _order = "name" def _get_default_category_id(self): if self._context.get('categ_id') or self._context.get('default_categ_id'): return self._context.get('categ_id') or self._context.get('default_categ_id') category = self.env.ref('product.product_category_all', raise_if_not_found=False) if not category: category = self.env['product.category'].search([], limit=1) if category: return category.id else: err_msg = _('You must define at least one product category in order to be able to create products.') redir_msg = _('Go to Internal Categories') raise RedirectWarning(err_msg, self.env.ref('product.product_category_action_form').id, redir_msg) def _get_default_uom_id(self): return self.env["product.uom"].search([], limit=1, order='id').id name = fields.Char('Name', index=True, required=True, translate=True) sequence = fields.Integer('Sequence', default=1, help='Gives the sequence order when displaying a product list') description = fields.Text( 'Description', translate=True, help="A precise description of the Product, used only for internal information purposes.") description_purchase = fields.Text( 'Purchase Description', translate=True, help="A description of the Product that you want to communicate to your vendors. " "This description will be copied to every Purchase Order, Receipt and Vendor Bill/Credit Note.") description_sale = fields.Text( 'Sale Description', translate=True, help="A description of the Product that you want to communicate to your customers. " "This description will be copied to every Sales Order, Delivery Order and Customer Invoice/Credit Note") type = fields.Selection([ ('consu', _('Consumable')), ('service', _('Service'))], string='Product Type', default='consu', required=True, help='A stockable product is a product for which you manage stock. The "Inventory" app has to be installed.\n' 'A consumable product, on the other hand, is a product for which stock is not managed.\n' 'A service is a non-material product you provide.\n' 'A digital content is a non-material product you sell online. The files attached to the products are the one that are sold on ' 'the e-commerce such as e-books, music, pictures,... The "Digital Product" module has to be installed.') rental = fields.Boolean('Can be Rent') categ_id = fields.Many2one( 'product.category', 'Internal Category', change_default=True, default=_get_default_category_id, required=True, help="Select category for the current product") currency_id = fields.Many2one( 'res.currency', 'Currency', compute='_compute_currency_id') # price fields price = fields.Float( 'Price', compute='_compute_template_price', inverse='_set_template_price', digits=dp.get_precision('Product Price')) list_price = fields.Float( 'Sales Price', default=1.0, digits=dp.get_precision('Product Price'), help="Base price to compute the customer price. Sometimes called the catalog price.") lst_price = fields.Float( 'Public Price', related='list_price', digits=dp.get_precision('Product Price')) standard_price = fields.Float( 'Cost', compute='_compute_standard_price', inverse='_set_standard_price', search='_search_standard_price', digits=dp.get_precision('Product Price'), groups="base.group_user", help = "Cost used for stock valuation in standard price and as a first price to set in average/fifo. " "Also used as a base price for pricelists. " "Expressed in the default unit of measure of the product. ") volume = fields.Float( 'Volume', compute='_compute_volume', inverse='_set_volume', help="The volume in m3.", store=True) weight = fields.Float( 'Weight', compute='_compute_weight', digits=dp.get_precision('Stock Weight'), inverse='_set_weight', store=True, help="The weight of the contents in Kg, not including any packaging, etc.") sale_ok = fields.Boolean( 'Can be Sold', default=True, help="Specify if the product can be selected in a sales order line.") purchase_ok = fields.Boolean('Can be Purchased', default=True) pricelist_id = fields.Many2one( 'product.pricelist', 'Pricelist', store=False, help='Technical field. Used for searching on pricelists, not stored in database.') uom_id = fields.Many2one( 'product.uom', 'Unit of Measure', default=_get_default_uom_id, required=True, help="Default Unit of Measure used for all stock operation.") uom_po_id = fields.Many2one( 'product.uom', 'Purchase Unit of Measure', default=_get_default_uom_id, required=True, help="Default Unit of Measure used for purchase orders. It must be in the same category than the default unit of measure.") company_id = fields.Many2one( 'res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('product.template'), index=1) packaging_ids = fields.One2many( 'product.packaging', string="Product Packages", compute="_compute_packaging_ids", inverse="_set_packaging_ids", help="Gives the different ways to package the same product.") seller_ids = fields.One2many('product.supplierinfo', 'product_tmpl_id', 'Vendors') variant_seller_ids = fields.One2many('product.supplierinfo', 'product_tmpl_id') active = fields.Boolean('Active', default=True, help="If unchecked, it will allow you to hide the product without removing it.") color = fields.Integer('Color Index') attribute_line_ids = fields.One2many('product.attribute.line', 'product_tmpl_id', 'Product Attributes') product_variant_ids = fields.One2many('product.product', 'product_tmpl_id', 'Products', required=True) # performance: product_variant_id provides prefetching on the first product variant only product_variant_id = fields.Many2one('product.product', 'Product', compute='_compute_product_variant_id') product_variant_count = fields.Integer( '# Product Variants', compute='_compute_product_variant_count') # related to display product product information if is_product_variant barcode = fields.Char('Barcode', oldname='ean13', related='product_variant_ids.barcode') default_code = fields.Char( 'Internal Reference', compute='_compute_default_code', inverse='_set_default_code', store=True) item_ids = fields.One2many('product.pricelist.item', 'product_tmpl_id', 'Pricelist Items') # image: all image fields are base64 encoded and PIL-supported image = fields.Binary( "Image", attachment=True, help="This field holds the image used as image for the product, limited to 1024x1024px.") image_medium = fields.Binary( "Medium-sized image", attachment=True, help="Medium-sized image of the product. It is automatically " "resized as a 128x128px image, with aspect ratio preserved, " "only when the image exceeds one of those sizes. Use this field in form views or some kanban views.") image_small = fields.Binary( "Small-sized image", attachment=True, help="Small-sized image of the product. It is automatically " "resized as a 64x64px image, with aspect ratio preserved. " "Use this field anywhere a small image is required.") @api.depends('product_variant_ids') def _compute_product_variant_id(self): for p in self: p.product_variant_id = p.product_variant_ids[:1].id @api.multi def _compute_currency_id(self): try: main_company = self.sudo().env.ref('base.main_company') except ValueError: main_company = self.env['res.company'].sudo().search([], limit=1, order="id") for template in self: template.currency_id = template.company_id.sudo().currency_id.id or main_company.currency_id.id @api.multi def _compute_template_price(self): prices = {} pricelist_id_or_name = self._context.get('pricelist') if pricelist_id_or_name: pricelist = None partner = self._context.get('partner') quantity = self._context.get('quantity', 1.0) # Support context pricelists specified as display_name or ID for compatibility if isinstance(pricelist_id_or_name, pycompat.string_types): pricelist_data = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1) if pricelist_data: pricelist = self.env['product.pricelist'].browse(pricelist_data[0][0]) elif isinstance(pricelist_id_or_name, pycompat.integer_types): pricelist = self.env['product.pricelist'].browse(pricelist_id_or_name) if pricelist: quantities = [quantity] * len(self) partners = [partner] * len(self) prices = pricelist.get_products_price(self, quantities, partners) for template in self: template.price = prices.get(template.id, 0.0) @api.multi def _set_template_price(self): if self._context.get('uom'): for template in self: value = self.env['product.uom'].browse(self._context['uom'])._compute_price(template.price, template.uom_id) template.write({'list_price': value}) else: self.write({'list_price': self.price}) @api.depends('product_variant_ids', 'product_variant_ids.standard_price') def _compute_standard_price(self): unique_variants = self.filtered(lambda template: len(template.product_variant_ids) == 1) for template in unique_variants: template.standard_price = template.product_variant_ids.standard_price for template in (self - unique_variants): template.standard_price = 0.0 @api.one def _set_standard_price(self): if len(self.product_variant_ids) == 1: self.product_variant_ids.standard_price = self.standard_price def _search_standard_price(self, operator, value): products = self.env['product.product'].search([('standard_price', operator, value)], limit=None) return [('id', 'in', products.mapped('product_tmpl_id').ids)] @api.depends('product_variant_ids', 'product_variant_ids.volume') def _compute_volume(self): unique_variants = self.filtered(lambda template: len(template.product_variant_ids) == 1) for template in unique_variants: template.volume = template.product_variant_ids.volume for template in (self - unique_variants): template.volume = 0.0 @api.one def _set_volume(self): if len(self.product_variant_ids) == 1: self.product_variant_ids.volume = self.volume @api.depends('product_variant_ids', 'product_variant_ids.weight') def _compute_weight(self): unique_variants = self.filtered(lambda template: len(template.product_variant_ids) == 1) for template in unique_variants: template.weight = template.product_variant_ids.weight for template in (self - unique_variants): template.weight = 0.0 @api.one def _set_weight(self): if len(self.product_variant_ids) == 1: self.product_variant_ids.weight = self.weight @api.one @api.depends('product_variant_ids.product_tmpl_id') def _compute_product_variant_count(self): self.product_variant_count = len(self.product_variant_ids) @api.depends('product_variant_ids', 'product_variant_ids.default_code') def _compute_default_code(self): unique_variants = self.filtered(lambda template: len(template.product_variant_ids) == 1) for template in unique_variants: template.default_code = template.product_variant_ids.default_code for template in (self - unique_variants): template.default_code = '' @api.one def _set_default_code(self): if len(self.product_variant_ids) == 1: self.product_variant_ids.default_code = self.default_code @api.depends('product_variant_ids', 'product_variant_ids.packaging_ids') def _compute_packaging_ids(self): for p in self: if len(p.product_variant_ids) == 1: p.packaging_ids = p.product_variant_ids.packaging_ids def _set_packaging_ids(self): for p in self: if len(p.product_variant_ids) == 1: p.product_variant_ids.packaging_ids = p.packaging_ids @api.constrains('uom_id', 'uom_po_id') def _check_uom(self): if any(template.uom_id and template.uom_po_id and template.uom_id.category_id != template.uom_po_id.category_id for template in self): raise ValidationError(_('Error: The default Unit of Measure and the purchase Unit of Measure must be in the same category.')) return True @api.onchange('uom_id') def _onchange_uom_id(self): if self.uom_id: self.uom_po_id = self.uom_id.id @api.model def create(self, vals): ''' Store the initial standard price in order to be able to retrieve the cost of a product template for a given date''' # TDE FIXME: context brol tools.image_resize_images(vals) template = super(ProductTemplate, self).create(vals) if "create_product_product" not in self._context: template.with_context(create_from_tmpl=True).create_variant_ids() # This is needed to set given values to first variant after creation related_vals = {} if vals.get('barcode'): related_vals['barcode'] = vals['barcode'] if vals.get('default_code'): related_vals['default_code'] = vals['default_code'] if vals.get('standard_price'): related_vals['standard_price'] = vals['standard_price'] if vals.get('volume'): related_vals['volume'] = vals['volume'] if vals.get('weight'): related_vals['weight'] = vals['weight'] if related_vals: template.write(related_vals) return template @api.multi def write(self, vals): tools.image_resize_images(vals) res = super(ProductTemplate, self).write(vals) if 'attribute_line_ids' in vals or vals.get('active'): self.create_variant_ids() if 'active' in vals and not vals.get('active'): self.with_context(active_test=False).mapped('product_variant_ids').write({'active': vals.get('active')}) return res @api.multi def copy(self, default=None): # TDE FIXME: should probably be copy_data self.ensure_one() if default is None: default = {} if 'name' not in default: default['name'] = _("%s (copy)") % self.name return super(ProductTemplate, self).copy(default=default) @api.multi def name_get(self): return [(template.id, '%s%s' % (template.default_code and '[%s] ' % template.default_code or '', template.name)) for template in self] @api.model def name_search(self, name='', args=None, operator='ilike', limit=100): # Only use the product.product heuristics if there is a search term and the domain # does not specify a match on `product.template` IDs. if not name or any(term[0] == 'id' for term in (args or [])): return super(ProductTemplate, self).name_search(name=name, args=args, operator=operator, limit=limit) Product = self.env['product.product'] templates = self.browse([]) while True: domain = templates and [('product_tmpl_id', 'not in', templates.ids)] or [] args = args if args is not None else [] products_ns = Product.name_search(name, args+domain, operator=operator) products = Product.browse([x[0] for x in products_ns]) templates |= products.mapped('product_tmpl_id') if (not products) or (limit and (len(templates) > limit)): break # re-apply product.template order + name_get return super(ProductTemplate, self).name_search( '', args=[('id', 'in', list(set(templates.ids)))], operator='ilike', limit=limit) @api.multi def price_compute(self, price_type, uom=False, currency=False, company=False): # TDE FIXME: delegate to template or not ? fields are reencoded here ... # compatibility about context keys used a bit everywhere in the code if not uom and self._context.get('uom'): uom = self.env['product.uom'].browse(self._context['uom']) if not currency and self._context.get('currency'): currency = self.env['res.currency'].browse(self._context['currency']) templates = self if price_type == 'standard_price': # standard_price field can only be seen by users in base.group_user # Thus, in order to compute the sale price from the cost for users not in this group # We fetch the standard price as the superuser templates = self.with_context(force_company=company and company.id or self._context.get('force_company', self.env.user.company_id.id)).sudo() prices = dict.fromkeys(self.ids, 0.0) for template in templates: prices[template.id] = template[price_type] or 0.0 if uom: prices[template.id] = template.uom_id._compute_price(prices[template.id], uom) # Convert from current user company currency to asked one # This is right cause a field cannot be in more than one currency if currency: prices[template.id] = template.currency_id.compute(prices[template.id], currency) return prices # compatibility to remove after v10 - DEPRECATED @api.model def _price_get(self, products, ptype='list_price'): return products.price_compute(ptype) @api.multi def create_variant_ids(self): Product = self.env["product.product"] AttributeValues = self.env['product.attribute.value'] for tmpl_id in self.with_context(active_test=False): # adding an attribute with only one value should not recreate product # write this attribute on every product to make sure we don't lose them variant_alone = tmpl_id.attribute_line_ids.filtered(lambda line: len(line.value_ids) == 1).mapped('value_ids') for value_id in variant_alone: updated_products = tmpl_id.product_variant_ids.filtered(lambda product: value_id.attribute_id not in product.mapped('attribute_value_ids.attribute_id')) updated_products.write({'attribute_value_ids': [(4, value_id.id)]}) # iterator of n-uple of product.attribute.value *ids* variant_matrix = [ AttributeValues.browse(value_ids) for value_ids in itertools.product(*(line.value_ids.ids for line in tmpl_id.attribute_line_ids if line.value_ids[:1].attribute_id.create_variant)) ] # get the value (id) sets of existing variants existing_variants = {frozenset(variant.attribute_value_ids.ids) for variant in tmpl_id.product_variant_ids} # -> for each value set, create a recordset of values to create a # variant for if the value set isn't already a variant to_create_variants = [ value_ids for value_ids in variant_matrix if set(value_ids.ids) not in existing_variants ] # check product variants_to_activate = self.env['product.product'] variants_to_unlink = self.env['product.product'] for product_id in tmpl_id.product_variant_ids: if not product_id.active and product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant) in variant_matrix: variants_to_activate |= product_id elif product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant) not in variant_matrix: variants_to_unlink |= product_id if variants_to_activate: variants_to_activate.write({'active': True}) # create new product for variant_ids in to_create_variants: new_variant = Product.create({ 'product_tmpl_id': tmpl_id.id, 'attribute_value_ids': [(6, 0, variant_ids.ids)] }) # unlink or inactive product for variant in variants_to_unlink: try: with self._cr.savepoint(), tools.mute_logger('odoo.sql_db'): variant.unlink() # We catch all kind of exception to be sure that the operation doesn't fail. except (psycopg2.Error, except_orm): variant.write({'active': False}) pass return True
class AASQualityRejectionWizard(models.TransientModel): _name = 'aas.quality.rejection.wizard' _description = 'AAS Quality Rejection Wizard' quality_id = fields.Many2one(comodel_name='aas.quality.order', string=u'质检单', ondelete='cascade') product_id = fields.Many2one(comodel_name='product.product', string=u'产品', ondelete='cascade') product_uom = fields.Many2one(comodel_name='product.uom', string=u'单位', ondelete='cascade') partner_id = fields.Many2one(comodel_name='res.partner', string=u'业务伙伴', ondelete='cascade') product_qty = fields.Float( string=u'数量', digits=dp.get_precision('Product Unit of Measure'), default=0.0) plot_lines = fields.One2many( comodel_name='aas.quality.rejection.lot.wizard', inverse_name='wizard_id', string=u'批次明细') label_lines = fields.One2many( comodel_name='aas.quality.rejection.label.wizard', inverse_name='wizard_id', string=u'标签明细') @api.one def action_check_lots(self): if not self.plot_lines or len(self.plot_lines) <= 0: raise UserError(u'您还没有添加批次拆分明细!') for plot in self.plot_lines: if float_compare(plot.label_qty, 0.0, precision_rounding=0.000001) <= 0.0: raise UserError(u'批次%s拆分标签每标签的数量不能小于零' % plot.product_lot.name) if float_compare(plot.label_qty, plot.product_qty, precision_rounding=0.000001) > 0.0: raise UserError(u'批次%s拆分标签每标签的数量不能大于批次总数' % plot.product_lot.name) @api.multi def action_split_lots(self): """ 批次拆分 :return: """ self.ensure_one() self.action_dolabels() view_form = self.env.ref( 'aas_quality.view_form_aas_quality_rejection_labels_wizard') return { 'name': u"不合格品标签", 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'aas.quality.rejection.wizard', 'views': [(view_form.id, 'form')], 'view_id': view_form.id, 'target': 'new', 'res_id': self.id, 'context': self.env.context } @api.one def action_dolabels(self): self.action_check_lots() label_lines = [] for plot in self.plot_lines: tproduct_qty, tlabel_qty = plot.product_qty, plot.label_qty for tindex in range( int(math.ceil(plot.product_qty / plot.label_qty))): if float_compare( tproduct_qty, 0.0, precision_rounding=0.000001) <= 0.0: break if float_compare(tproduct_qty, plot.label_qty, precision_rounding=0.000001) < 0.0: tlabel_qty = tproduct_qty label_lines.append((0, 0, { 'product_lot': plot.product_lot.id, 'label_qty': tlabel_qty, 'origin_order': plot.origin_order, 'commit_id': plot.commit_id, 'commit_model': plot.commit_model, 'commit_order': plot.commit_order })) tproduct_qty -= tlabel_qty self.write({'label_lines': label_lines}) @api.one def action_done(self): rejection_lines = [] templocation = self.env['stock.warehouse'].get_default_warehouse( ).wh_input_stock_loc_id for tline in self.label_lines: tempvals = { 'product_id': self.product_id.id, 'product_uom': self.product_uom.id, 'partner_id': self.partner_id and self.partner_id.id } tempvals.update({ 'location_id': templocation.id, 'product_lot': tline.product_lot.id, 'product_qty': tline.label_qty, 'origin_order': tline.origin_order, 'locked': True, 'locked_order': tline.commit_order, 'stocked': True, 'state': 'normal', 'qualified': False }) templabel = self.env['aas.product.label'].create(tempvals) rejection_lines.append((0, 0, { 'label_id': templabel.id, 'product_id': tempvals['product_id'], 'product_uom': tempvals['product_uom'], 'product_lot': tempvals['product_lot'], 'product_qty': tempvals['product_qty'], 'origin_order': tempvals['origin_order'], 'current_label': True, 'commit_id': tline.commit_id, 'commit_model': tline.commit_model, 'commit_order': tline.commit_order })) self.quality_id.write({'rejection_lines': rejection_lines}) self.quality_id.action_quality_done()
class Predict(models.Model): _name = 'tsbd.predict' trig = fields.Boolean() match_state = fields.Char(related='match_id.state', store=True) match_id = fields.Many2one('tsbd.match') site_id = fields.Many2one('tsbd.site',string='web site') link = fields.Char() predict_score1 = fields.Integer() predict_score2 = fields.Integer() predict_handicap = fields.Selection([('handicap1','handicap1'),('handicap2','handicap2')],compute='predict_handicap_and_ou_',store=True) amount = fields.Float(default=1) predict_exact_score_winning_amount = fields.Float(compute='predict_exact_score_winning_amount_',store=True) state = fields.Selection([('nhap_tay',u'Nhập tay'),('tu_dong','Tự động'),('can_read_du_doan','Không đọc được dự đoán')],default = 'nhap_tay') time= fields.Datetime(related='match_id.time', store=True) date = fields.Date (related='match_id.date', store=True) team1= fields.Many2one('tsbd.team',related='match_id.team1', store=True) team2= fields.Many2one('tsbd.team',related='match_id.team2', store=True) begin_handicap = fields.Float(digit=(6,2),related='match_id.begin_handicap', store=True) begin_ou= fields.Float(digit=(6,2),related='match_id.begin_ou', store=True) # state_match = fields.Char(related = 'match_id.state',store=True) @api.onchange('link') def link_oc_(self): if self.link: rs = re.search('//(.+?)/',self.link) site_name = rs.group(1).replace('www.','') site_id = get_or_create_object_sosanh (self,'tsbd.site',{'name':site_name}) self.site_id = site_id @api.depends('match_id.begin_ou','match_id.begin_handicap','predict_score1','predict_score2') def predict_handicap_and_ou_(self): for r in self: diff = (r.predict_score1 - r.predict_score2) - r.match_id.begin_handicap if diff > 0: predict_handicap = 'handicap1' else: predict_handicap = 'handicap2' r.predict_handicap = predict_handicap sum_score = r.predict_score1 + r.predict_score2 if sum_score > r.match_id.begin_ou: predict_ou = 'over' else: predict_ou = 'under' r.predict_ou = predict_ou predict_ou = fields.Selection([('over','over'),('under','under')],compute='predict_handicap_and_ou_',store=True) predict_handicap_winning_mount = fields.Float(compute='predict_handicap_winning_mount_',store=True) predict_ou_winning_mount = fields.Float(compute='predict_ou_winning_mount_',store=True) @api.depends('predict_ou','trig', 'match_state') @adecorator def predict_ou_winning_mount_(self): for r in self: bet_kind = r.predict_ou winning_ratio,winning_amount = handicap_winning_(r,bet_kind,mode='predict') r.predict_ou_winning_mount = winning_amount @api.depends('predict_handicap','trig','match_state') @adecorator def predict_handicap_winning_mount_(self): for r in self: bet_kind = r.predict_handicap winning_ratio,winning_amount = handicap_winning_(r,bet_kind,mode='predict') r.predict_handicap_winning_mount = winning_amount @api.depends('predict_score1','predict_score2','trig','match_state') @adecorator def predict_exact_score_winning_amount_(self): for r in self: bet_kind = 'exact_score' winning_ratio,winning_amount = handicap_winning_(r,bet_kind,mode='predict') r.predict_exact_score_winning_amount = winning_amount
class SRDCMixDesign(models.Model): _name = "srdc.mix.design" name = fields.Char() active = fields.Boolean(default=True) product_id = fields.Many2one('product.product') product_qty = fields.Float(default=1) sequence = fields.Integer() line_ids = fields.One2many('srdc.mix.design.line', 'mix_design_id', srting='Mix_Design') is_auto = fields.Boolean() total_amount = fields.Float(compute='_compute_total_amount', store=True) optimize_total_amount = fields.Float(compute='_compute_total_amount', store=True) is_standard = fields.Boolean('Standard?') state = fields.Selection([ ('draft', 'Draft'), ('confirmed', 'Confirmed'), ('lab_test', 'LAB Test'), ('validated', 'Validated'), ('done', 'Done'), ('pending', 'Pending'), ('cancel', 'Cancel'), ], default='draft') _sql_constraints = [ ('name_uniq', 'unique (name)', "Code name already exists !"), ] @api.model def create(self, vals): if not self.search([('is_standard', '=', True)]) and not vals.get('is_standard'): vals['is_standard'] = True return super(SRDCMixDesign, self).create(vals) @api.model def default_get(self, fields): defaults = super(SRDCMixDesign, self).default_get(fields) material_ids = self.env['product.product'].search([ ('categ_id.parent_id', '=', self.env.ref('srdc_qc.product_category_material').id) ], order='id') defaults['line_ids'] = [ (0, 0, { 'product_id': material.id, 'product_tmpl_id': material.product_tmpl_id.id, 'categ_id': material.categ_id.id, 'quantity': 0, 'sequence': material.id, 'oum_id': material.uom_id.id }) for material in material_ids] return defaults @api.multi @api.depends('line_ids.quantity', 'line_ids.product_id', 'line_ids.product_id.standard_price', 'line_ids.supplierinfo_id', 'line_ids.supplierinfo_id.price') def _compute_total_amount(self): for rec in self: sum = 0 optimize_sum = 0 for line in rec.line_ids.filtered( lambda l: l.quantity > 0 and ( l.product_id.standard_price > 0 or l.supplierinfo_id and l.supplierinfo_id.price > 0)): if line.supplierinfo_id: sum += line.supplierinfo_id.price * line.quantity optimize_sum += line.supplierinfo_id.optimize_price * line.quantity else: sum += line.product_id.standard_price * line.quantity optimize_sum += line.product_id.optimize_price * line.quantity rec.total_amount = sum rec.optimize_total_amount = optimize_sum @api.multi def btn_confirm(self): self.write({'state': 'confirmed'}) @api.multi def btn_start_lab_test(self): self.write({'state': 'lab_test'}) @api.multi def btn_validate(self): self.write({'state': 'validated'}) @api.multi def btn_approve(self): self.write({'state': 'done'}) @api.multi def btn_set_to_draft(self): self.write({'state': 'draft'}) @api.multi def btn_cancel(self): self.write({'state': 'cancel'})
class CurrencyRate(models.Model): _inherit = "res.currency.rate" rate = fields.Float( digits=(12, 16), help='The rate of the currency to the currency of rate 1')
class PR(models.Model): _name = 'pr.pr' _rec_name = 'name' name = fields.Char('Name') date_pr = fields.Datetime('PR Date', required=True) partner_id = fields.Many2one('res.partner', string='Vendor', required=True) project_id = fields.Many2one('pr.project', string='Project') approve_id = fields.Many2one('res.users', string='Approver') amount_untaxed = fields.Float(string='Untaxed Amount', store=True, readonly=True) amount_tax = fields.Float(string='Taxes', store=True, readonly=True) amount_total = fields.Float(string='Total', store=True, readonly=True) note = fields.Text('Note') state = fields.Selection([ ('draft', 'Draft'), ('revise', 'Revise'), ('approve', 'Approve'), ('cancel', 'Cancel'), ], string='Status', default='draft') pr_lines = fields.One2many('pr.pr_line', 'pr_id', string='PR Line') company_id = fields.Many2one('res.company', default=lambda self: self.env.user.company_id.id) def get_baht_text(self): return bahttext(self.amount_total) @api.model def create(self, vals): seq = self.env['ir.sequence'].next_by_code('pr.pr_num') or '-' vals['name'] = seq return super(PR, self).create(vals) @api.onchange('pr_lines') def get_total_amount(self): amount_untaxed = 0 tax_rate = self.env.user.company_id.account_purchase_tax_id.amount or 7 for r in self: for item in r.pr_lines: amount_untaxed += item.amount self.amount_untaxed = amount_untaxed self.amount_tax = (self.amount_untaxed * tax_rate) / 100 self.amount_total = self.amount_untaxed + self.amount_tax def do_pr_draft(self): self.write({'state': 'draft'}) def do_pr_approve(self): if not self.project_id.budget: raise exceptions.ValidationError(_('Project budget invalid amount!')) if self.amount_total > self.project_id.budget: raise exceptions.ValidationError(_('This PR is over budget!!')) self.create_rfq() self.write({'state': 'approve'}) def create_rfq(self): rfq = self.env['purchase.order'].sudo().create({ 'partner_id': self.partner_id.id, 'state': 'draft', }) for line in self.pr_lines: rfq_line = self.env['purchase.order.line'].sudo().create({ 'order_id': rfq.id, 'name': line.name, 'product_id': line.product_id.id, 'product_qty': line.qty, 'product_uom': line.product_id.uom_id.id, 'price_unit': line.price, 'price_subtotal': line.amount, 'date_planned': datetime.now(), }) def do_pr_cancel(self): return { 'view_type': 'form', 'view_mode': 'form', 'res_model': 'pr.cancel.wizard', 'target': 'new', 'type': 'ir.actions.act_window', 'context': { 'default_pr_id': self.id, 'default_pr_name': self.name, } } def do_add_products(self): return { 'view_type': 'form', 'view_mode': 'form', 'res_model': 'product.multi.wizard', 'target': 'new', 'type': 'ir.actions.act_window', 'context': { 'default_pr_id': self.id, } }
class HrPayslip(models.Model): _name = 'hr.payslip' _inherit = ['mail.thread', 'hr.payslip'] #added by sangita refund_id = fields.Many2one('hr.payslip', string="Refund ID") @api.model def _get_currency(self): return self.env.user.company_id.currency_id.id currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, required=True, default=lambda self: self._get_currency()) def compute_difference_two_date(self): s = datetime.strptime(self.date_from, "%Y-%m-%d") e = datetime.strptime(self.date_to, "%Y-%m-%d") start = s.day end = e.day date_days = end - start return date_days # deadline_days = (date.today() - deadline_date).days # def convert_number2word_inv(self): for line in self.line_ids: if line.code == 'NET': return self.env['convert.num2word'].convert_number2word( line.amount, 'en_US', self.currency_id.name) def leaves_type_cal_earned(self): earned = 0 res = self.env['hr.holidays'].search([ ('holiday_type', '=', 'employee'), ('holiday_status_id.limit', '=', False), ('state', '!=', 'refuse'), ('employee_id', '=', self.employee_id.id), ('earned_leaves', '=', True) ]) for r in res: earned = r.number_of_days_temp return earned def leaves_type_cal_sick(self): sick = 0 res = self.env['hr.holidays'].search([ ('holiday_type', '=', 'employee'), ('holiday_status_id.limit', '=', False), ('state', '!=', 'refuse'), ('employee_id', '=', self.employee_id.id), ('sick_leaves', '=', True) ]) for r in res: sick = r.number_of_days_temp return sick def leaves_type_cal_casual(self): casual = 0 res = self.env['hr.holidays'].search([ ('holiday_type', '=', 'employee'), ('holiday_status_id.limit', '=', False), ('state', '!=', 'refuse'), ('employee_id', '=', self.employee_id.id), ('casual_leaves', '=', True) ]) for r in res: casual = r.number_of_days_temp return casual def unused_leaves_cal(self): unused = 0 a = [] unused_leaves_id = self.env['hr.holidays'].search([ ('holiday_type', '=', 'employee'), ('employee_id', '=', self.employee_id.id), ('type', '=', 'add'), ('state', 'in', ('confirm', 'validate')) ]) for i in unused_leaves_id: a += [i.number_of_days] unused = sum(a) return unused def used_leaves_cal(self): used = 0 a = [] used_leaves_id = self.env['hr.holidays'].search([ ('holiday_type', '=', 'employee'), ('employee_id', '=', self.employee_id.id), ('type', '=', 'remove'), ('state', 'in', ('confirm', 'validate')) ]) for i in used_leaves_id: a += [-i.number_of_days] used = sum(a) return used #added by sangita def compute_net_pay(self): loan_amount = 0.0 if self.line_ids: for line in self.line_ids: if line.code == 'LOAN': loan_amount = line.amount if line.code == 'NET': net = line.amount - loan_amount return net #added by sangita @api.multi @api.depends('employee_id', 'employee_id.leaves_count') def _compute_leaves_count(self): for t in self: for d in t.employee_id: t.leaves_count = d.leaves_count #akhodifad @api.constrains('date_from', 'date_to', 'employee_id') def _compute_ytd_count(self): for t in self: if t.employee_id: to_date = t.date_to.split("-") SQL = """ select ps.employee_id, sum(psl.total) from hr_payslip_line psl inner join hr_payslip ps on ps.id=psl.slip_id and extract(year from ps.date_to) = %s and ps.employee_id = %s and ps.company_id = %s and code = 'NET' group by ps.employee_id, ps.company_id; """ self.env.cr.execute(SQL, ( int(to_date[0]), t.employee_id.id, t.company_id.id or None, )) res = self.env.cr.fetchall() if res: t.ytd_count = float(res[0][1]) def _get_cash_and_bank(self): type_id = False type_id = self.env.ref('account.data_account_type_liquidity').id return [('user_type_id', '=', type_id)] leaves_count = fields.Integer('Number of Leaves', compute='_compute_leaves_count') ytd_count = fields.Float('Number of YTD', compute='_compute_ytd_count') account_id = fields.Many2one('account.account', 'Account', domain=_get_cash_and_bank) pay_odr_id = fields.Many2one('payment.order', string="Payment Order") #added by sangita @api.multi @api.onchange('contract_id') def onchange_contract(self): super(HrPayslip, self).onchange_contract() self.account_id = self.contract_id.account_id.id # #added by sangita @api.constrains('contract_id') def onchange_contract_to_account(self): self.account_id = self.contract_id.account_id.id @api.multi def create_account_voucher(self): ac_voucher = self.env['account.voucher'] ac_exit = ac_voucher.search([('name', '=', self.name)]) if self.paid: raise UserError(_('Payment Order already created.')) ac_vc_lin = self.env['account.voucher.line'] vals_ac = { 'name': self.number or None, 'pay_now': 'pay_now', 'voucher_type': 'purchase', 'date': self.date, 'journal_id': self.journal_id.id, 'account_id': self.account_id.id, 'move_id': self.move_id.id or None, 'payslip_id': self.id, } ac_obj = ac_voucher.create(vals_ac) self.paid = True for l in self.line_ids: if l.salary_rule_id.code.capitalize() == 'Net': price = l.amount vals_ac_lin = { 'voucher_id': ac_obj.id, 'name': self.name, 'account_id': l.salary_rule_id.account_credit.id or self.account_id.id, 'quantity': 1.0, 'price_unit': price, 'company_id': self.company_id.id, } ac_obj = ac_vc_lin.sudo().create(vals_ac_lin) #added by Sangita @api.multi def dummy_method_ytd(self): year_line = self.env['hr.payslip.year'].search([]) year_line.unlink() for t in self: if t.employee_id and t.date_from: today = str(date.today()) fiscal_year = self.env['account.fiscalyear'].search( [('date_start', '<=', today), ('date_stop', '>=', today)], limit=1) SQL = """ select hp.employee_id, hpl.salary_rule_id,sum(hpl.total) from hr_payslip_line as hpl inner join hr_payslip as hp on hpl.slip_id = hp.id where hp.employee_id = %s and hp.state in ('done') and hp.date_from >= %s and date_to <= %s group by hpl.id ,hpl.salary_rule_id,hp.employee_id,hp.state """ self.env.cr.execute( SQL, (t.employee_id.id, str( fiscal_year.date_start), str(t.date_to))) res = self.env.cr.fetchall() if res: for line in res: # print"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",line self.env['hr.payslip.year'].create({ 'salary_rule_id': line[1], 'hr_payslip_id': self.id, 'total': line[2], }) return { 'name': _('Leaves'), 'view_type': 'form', 'view_mode': 'tree,pivot,from', 'res_model': 'hr.payslip.year', 'src_model': 'hr.payslip', 'type': 'ir.actions.act_window', 'context': { 'search_default_employee_id': self.employee_id.id, 'default_employee_id': self.employee_id.id, 'group_by': 'salary_rule_id' }, 'search_view_id': self.env.ref('payslip_batch_extended.hr_payslip_year_tree').id } #added by sangita @api.multi def tod_calculate(self): for t in self: if t.employee_id and t.date_from: today = str(date.today()) fiscal_year = self.env['account.fiscalyear'].search( [('date_start', '<=', today), ('date_stop', '>=', today)], limit=1) SQL = """ select hpl.salary_rule_id, hp.employee_id, hsr.name as name,sum(hpl.total) as tot from hr_payslip_line as hpl inner join hr_payslip as hp on hpl.slip_id = hp.id inner join hr_salary_rule hsr on hpl.salary_rule_id= hsr.id where hp.employee_id = %s and hp.state in ('done') and hp.date_from >= %s and date_to <= %s group by hpl.salary_rule_id,hp.employee_id,hsr.name order by hpl.salary_rule_id """ self.env.cr.execute( SQL, (t.employee_id.id, str( fiscal_year.date_start), str(t.date_to))) res = self.env.cr.fetchall() print "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh", res return res @api.multi def view_account_voucher(self): return { 'name': _('Payment Order'), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.voucher', 'type': 'ir.actions.act_window', 'domain': [('payslip_id', '=', self.id)], } #added by sangita @api.multi def hr_employee_holiday_request_leave_left(self): return { 'name': _('Leaves'), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'hr.holidays', 'src_model': 'hr.employee', 'type': 'ir.actions.act_window', 'domain': [('holiday_type', '=', 'employee'), ('holiday_status_id.limit', '=', False), ('state', '!=', 'refuse')], 'context': { 'search_default_employee_id': self.employee_id.id, 'default_employee_id': self.employee_id.id, 'search_default_group_type': 1, 'search_default_year': 1 }, 'search_view_id': self.env.ref('hr_holidays.view_hr_holidays_filter').id } #added by sangita @api.multi def compute_sheet(self): # print"@@@@@@@@@@@@@@@@@@@@@@@@@@WWWWWWWWWWWWW" # for contract in self.contract_id: # self.account_id = contract.account_id.id # print"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",s.account_id for s in self: for loan in s.loan_ids: if loan.date <= s.date_to: loan.paid = True return super(HrPayslip, s).compute_sheet() @api.multi def refund_sheet(self): res = super(HrPayslip, self).refund_sheet() self.state = 'cancel' for s in self: for loan in s.loan_ids: if loan.date <= s.date_to: loan.paid = False self.refund_id = res.ids return True @api.multi def refund_sheet(self): for payslip in self: copied_payslip = payslip.copy({ 'credit_note': True, 'name': _('Refund: ') + payslip.name }) copied_payslip.action_payslip_done() payslip.state = 'cancel' for loan in payslip.loan_ids: if loan.date <= payslip.date_to: loan.paid = False # copied_payslip = self.copy({'credit_note': True, 'name': _('Refund: ') + s.name}) payslip.refund_id = copied_payslip formview_ref = self.env.ref('hr_payroll.view_hr_payslip_form', False) treeview_ref = self.env.ref('hr_payroll.view_hr_payslip_tree', False) # print"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<,",copied_payslip.ids return { 'name': ("Refund Payslip"), 'view_mode': 'tree, form', 'view_id': False, 'view_type': 'form', 'res_model': 'hr.payslip', 'type': 'ir.actions.do_nothing', 'target': 'current', 'domain': "[('id', 'in', %s)]" % copied_payslip.ids, 'views': [(treeview_ref and treeview_ref.id or False, 'tree'), (formview_ref and formview_ref.id or False, 'form')], 'context': {} } @api.model def create(self, vals): res = super(HrPayslip, self).create(vals) res.get_loan() return res @api.multi def action_payslip_done(self): res = super(HrPayslip, self).action_payslip_done() print "???????????????????????????????????????????" if self.state == 'done': lwp_id = self.env['hr.holidays'].search([('employee_id', '=', self.employee_id.id)]) for lwp in lwp_id: print "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", lwp_id lwp.payslip_status = True lwp.payslip_id = self.id return res #Added by Sangita When in payslip select from date is jan and to date is feb the payslip name genereated by date to name @api.onchange('employee_id', 'date_from', 'date_to') def onchange_employee(self): if (not self.employee_id) or (not self.date_from) or ( not self.date_to): return employee = self.employee_id date_from = self.date_from date_to = self.date_to ttyme = datetime.fromtimestamp( time.mktime(time.strptime(date_to, "%Y-%m-%d"))) locale = self.env.context.get('lang', 'en_US') self.name = _('Salary Slip of %s for %s') % ( employee.name, tools.ustr( babel.dates.format_date( date=ttyme, format='MMMM-y', locale=locale))) self.company_id = employee.company_id if not self.env.context.get('contract') or not self.contract_id: contract_ids = self.get_contract(employee, date_from, date_to) if not contract_ids: return self.contract_id = self.env['hr.contract'].browse(contract_ids[0]) if not self.contract_id.struct_id: return self.struct_id = self.contract_id.struct_id #computation of the salary input worked_days_line_ids = self.get_worked_day_lines( contract_ids, date_from, date_to) worked_days_lines = self.worked_days_line_ids.browse([]) for r in worked_days_line_ids: worked_days_lines += worked_days_lines.new(r) self.worked_days_line_ids = worked_days_lines input_line_ids = self.get_inputs(contract_ids, date_from, date_to) input_lines = self.input_line_ids.browse([]) for r in input_line_ids: input_lines += input_lines.new(r) self.input_line_ids = input_lines return
class purchase_order(models.Model): _inherit = 'purchase.order' @api.model def create(self, vals): res = super(purchase_order, self).create(vals) discount_type_obj = self.env['discount.type'] discount_type_percent = self.env['ir.model.data'].xmlid_to_res_id( 'bi_sale_purchase_invoice_discount.discount_type_percent_id') discount_type_fixed = self.env['ir.model.data'].xmlid_to_res_id( 'bi_sale_purchase_invoice_discount.discount_type_fixed_id') if vals.get('discount_value'): if vals.get('discount_type_id') == discount_type_percent: brw_type = discount_type_obj.browse( discount_type_percent).discount_value if brw_type > 0.0: if vals.get('discount_value', 0.00) > brw_type: raise UserError( _('You can not set Discount Value more then %s . \n Maximum Discount value is %s \n Set maximum value Purchase-> Configuration-> Discount Type') % \ (brw_type , brw_type)) elif vals.get('discount_type_id') == discount_type_fixed: brw_type = discount_type_obj.browse( discount_type_fixed).discount_value if brw_type > 0.0: if vals.get('discount_value', 0.00) > brw_type: raise UserError( _('You can not set Discount Value more then %s. \n Maximum Discount value is %s \n Set maximum value Purchase-> Configuration-> Discount Type' ) % \ (brw_type ,brw_type )) return res def write(self, vals): res = super(purchase_order, self).write(vals) discount_type_percent = self.env['ir.model.data'].xmlid_to_res_id( 'bi_sale_purchase_invoice_discount.discount_type_percent_id') discount_type_fixed = self.env['ir.model.data'].xmlid_to_res_id( 'bi_sale_purchase_invoice_discount.discount_type_fixed_id') discount_type_obj = self.env['discount.type'] if vals.get( 'discount_type_id' ) == discount_type_percent or self.discount_type_id.id == discount_type_percent: brw_type = discount_type_obj.browse( discount_type_percent).discount_value if brw_type > 0.0: if vals.get('discount_value', 0.00) > brw_type: raise UserError( _('You can not set Discount Value more then %s . \n Maximum Discount value is %s \n Set maximum value Purchase-> Configuration-> Discount Type') % \ (brw_type , brw_type)) if vals.get( 'discount_type_id' ) == discount_type_fixed or self.discount_type_id.id == discount_type_fixed: brw_type = discount_type_obj.browse( discount_type_fixed).discount_value if brw_type > 0.0: if vals.get('discount_value', 0.00) > brw_type: raise UserError( _('You can not set Discount Value more then %s. \n Maximum Discount value is %s \n Set maximum value Purchase-> Configuration-> Discount Type' ) % \ (brw_type ,brw_type )) if vals.get('discount_value'): if self.discount_type_id.id == discount_type_percent: brw_type = discount_type_obj.browse( discount_type_percent).discount_value if brw_type > 0.0: if vals.get('discount_value', 0.00) > brw_type: raise UserError( _('You can not set Discount Value more then %s. \n Maximum Discount value is %s \n Set maximum value Purchase-> Configuration-> Discount Type') % \ (brw_type , brw_type)) elif self.discount_type_id.id == discount_type_fixed: brw_type = discount_type_obj.browse( discount_type_fixed).discount_value if brw_type > 0.0: if vals.get('discount_value', 0.00) > brw_type: raise UserError( _('You can not set Discount Value more then %s. \n Maximum Discount value is %s \n Set maximum value Purchase-> Configuration-> Discount Type' ) % \ (brw_type ,brw_type )) return res @api.onchange('apply_discount') def onchange_apply_discount(self): if self.apply_discount: account_search = self.env['account.account'].search([ ('discount_account', '=', True), ('user_type_id.internal_group', '=', 'income') ]) if account_search: self.discount_account = account_search[0].id @api.depends('order_line.price_total', 'discount_value', 'discount_type_id', 'apply_discount') def _amount_all(self): for order in self: amount_untaxed = amount_tax = 0.0 for line in order.order_line: amount_untaxed += line.price_subtotal amount_tax += line.price_tax if not order.apply_discount: order.amount_after_discount = 0.0 if order.amount_after_discount: order.update({ 'amount_untaxed': order.currency_id.round(amount_untaxed), 'amount_tax': order.currency_id.round(amount_tax), 'amount_total': order.amount_after_discount + order.currency_id.round(amount_tax), }) else: order.update({ 'amount_untaxed': order.currency_id.round(amount_untaxed), 'amount_tax': order.currency_id.round(amount_tax), 'amount_total': amount_untaxed + amount_tax, }) @api.depends('discount_value', 'order_line.price_total', 'discount_type_id') def _compute_amount_after_discount(self): discount = 0.0 amount_untaxed = 0.0 discount_type_percent = self.env['ir.model.data'].xmlid_to_res_id( 'bi_sale_purchase_invoice_discount.discount_type_percent_id') discount_type_fixed = self.env['ir.model.data'].xmlid_to_res_id( 'bi_sale_purchase_invoice_discount.discount_type_fixed_id') for self_obj in self: for line in self_obj.order_line: amount_untaxed += line.price_subtotal if self_obj.discount_type_id.id == discount_type_fixed: discount = amount_untaxed - self_obj.discount_value self_obj.amount_after_discount = discount elif self_obj.discount_type_id.id == discount_type_percent: discount_percent = amount_untaxed * ( (self_obj.discount_value or 0.0) / 100.0) discount = amount_untaxed - discount_percent self_obj.amount_after_discount = discount else: self_obj.amount_after_discount = discount apply_discount = fields.Boolean('Apply Discount') discount_type_id = fields.Many2one('discount.type', 'Discount Type') discount_value = fields.Float('Purchase Discount') discount_account = fields.Many2one('account.account', 'Discount Account') amount_after_discount = fields.Monetary( 'Amount After Discount', store=True, readonly=True, compute='_compute_amount_after_discount')
class AccountPayment(models.Model): _inherit = "account.payment" payment_group_id = fields.Many2one( 'account.payment.group', 'Payment Group', ondelete='cascade', readonly=True, ) # we add this field so company can be send in context when adding payments # before payment group is saved payment_group_company_id = fields.Many2one( related='payment_group_id.company_id', readonly=True, ) # we make a copy without transfer option, we try with related but it # does not works payment_type_copy = fields.Selection(selection=[('outbound', 'Send Money'), ('inbound', 'Receive Money')], compute='_compute_payment_type_copy', inverse='_inverse_payment_type_copy', string='Payment Type') signed_amount = fields.Monetary( string='Payment Amount', compute='_compute_signed_amount', ) signed_amount_company_currency = fields.Monetary( string='Payment Amount on Company Currency', compute='_compute_signed_amount', currency_field='company_currency_id', ) amount_company_currency = fields.Monetary( string='Payment Amount on Company Currency', compute='_compute_amount_company_currency', inverse='_inverse_amount_company_currency', currency_field='company_currency_id', ) other_currency = fields.Boolean(compute='_compute_other_currency', ) force_amount_company_currency = fields.Monetary( string='Payment Amount on Company Currency', currency_field='company_currency_id', copy=False, ) exchange_rate = fields.Float( string='Exchange Rate', compute='_compute_exchange_rate', # readonly=False, # inverse='_inverse_exchange_rate', digits=(16, 4), ) company_currency_id = fields.Many2one( related='company_id.currency_id', readonly=True, ) @api.multi @api.depends('amount', 'payment_type', 'partner_type', 'amount_company_currency') def _compute_signed_amount(self): for rec in self: sign = 1.0 if ((rec.partner_type == 'supplier' and rec.payment_type == 'inbound') or (rec.partner_type == 'customer' and rec.payment_type == 'outbound')): sign = -1.0 rec.signed_amount = rec.amount and rec.amount * sign rec.signed_amount_company_currency = ( rec.amount_company_currency and rec.amount_company_currency * sign) @api.multi @api.depends('currency_id', 'company_currency_id') def _compute_other_currency(self): for rec in self: if rec.company_currency_id and rec.currency_id and \ rec.company_currency_id != rec.currency_id: rec.other_currency = True @api.multi @api.depends('amount', 'other_currency', 'amount_company_currency') def _compute_exchange_rate(self): for rec in self.filtered('other_currency'): rec.exchange_rate = rec.amount and (rec.amount_company_currency / rec.amount) or 0.0 @api.multi # this onchange is necesary because odoo, sometimes, re-compute # and overwrites amount_company_currency. That happends due to an issue # with rounding of amount field (amount field is not change but due to # rouding odoo believes amount has changed) @api.onchange('amount_company_currency') def _inverse_amount_company_currency(self): _logger.info('Running inverse amount company currency') for rec in self: if rec.other_currency and rec.amount_company_currency != \ rec.currency_id.with_context( date=rec.payment_date).compute( rec.amount, rec.company_id.currency_id): force_amount_company_currency = rec.amount_company_currency else: force_amount_company_currency = False rec.force_amount_company_currency = force_amount_company_currency @api.multi @api.depends('amount', 'other_currency', 'force_amount_company_currency') def _compute_amount_company_currency(self): """ * Si las monedas son iguales devuelve 1 * si no, si hay force_amount_company_currency, devuelve ese valor * sino, devuelve el amount convertido a la moneda de la cia """ _logger.info('Computing amount company currency') for rec in self: if not rec.other_currency: amount_company_currency = rec.amount elif rec.force_amount_company_currency: amount_company_currency = rec.force_amount_company_currency else: amount_company_currency = rec.currency_id.with_context( date=rec.payment_date).compute(rec.amount, rec.company_id.currency_id) rec.amount_company_currency = amount_company_currency @api.multi @api.onchange('payment_type_copy') def _inverse_payment_type_copy(self): for rec in self: # if false, then it is a transfer rec.payment_type = (rec.payment_type_copy and rec.payment_type_copy or 'transfer') @api.multi @api.depends('payment_type') def _compute_payment_type_copy(self): for rec in self: if rec.payment_type == 'transfer': continue rec.payment_type_copy = rec.payment_type @api.multi def get_journals_domain(self): domain = super(AccountPayment, self).get_journals_domain() if self.payment_group_company_id: domain.append( ('company_id', '=', self.payment_group_company_id.id)) return domain @api.onchange('payment_type') def _onchange_payment_type(self): """ we disable change of partner_type if we came from a payment_group but we still reset the journal """ if not self._context.get('payment_group'): return super(AccountPayment, self)._onchange_payment_type() self.journal_id = False @api.multi @api.constrains('payment_group_id', 'payment_type') def check_payment_group(self): # odoo tests don't create payments with payment gorups if self.env.registry.in_test_mode(): return True for rec in self: if rec.partner_type and not rec.payment_group_id: raise ValidationError( _('Payments with partners must be created from ' 'payments groups')) # transfers or payments from bank reconciliation without partners elif not rec.partner_type and rec.payment_group_id: raise ValidationError( _("Payments without partners (usually transfers) cant't " "have a related payment group")) @api.model def create(self, vals): # when payments are created from bank reconciliation create the # payment group before creating payment to aboid raising error if vals.get('state') == 'reconciled' and vals.get('partner_type'): company_id = self.env['account.journal'].browse( vals.get('journal_id')).company_id.id payment_group = self.env['account.payment.group'].create({ 'company_id': company_id, 'partner_type': vals.get('partner_type'), 'partner_id': vals.get('partner_id'), 'payment_date': vals.get('payment_date'), 'communication': vals.get('communication'), 'state': 'posted', }) vals['payment_group_id'] = payment_group.id payment = super(AccountPayment, self).create(vals) return payment @api.multi @api.depends('invoice_ids', 'payment_type', 'partner_type', 'partner_id') def _compute_destination_account_id(self): """ If we are paying a payment gorup with paylines, we use account of lines that are going to be paid """ for rec in self: to_pay_account = rec.payment_group_id.to_pay_move_line_ids.mapped( 'account_id') if len(to_pay_account) > 1: raise ValidationError( _('To Pay Lines must be of the same account!')) elif len(to_pay_account) == 1: rec.destination_account_id = to_pay_account[0] else: super(AccountPayment, rec)._compute_destination_account_id() @api.multi def show_details(self): """ Metodo para mostrar form editable de payment, principalmente para ser usado cuando hacemos ajustes y el payment group esta confirmado pero queremos editar una linea """ return { 'name': _('Payment Lines'), 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'account.payment', 'target': 'new', 'res_id': self.id, 'context': self._context, } def _get_shared_move_line_vals(self, debit, credit, amount_currency, move_id, invoice_id=False): """ Si se esta forzando importe en moneda de cia, usamos este importe para debito/credito """ res = super(AccountPayment, self)._get_shared_move_line_vals(debit, credit, amount_currency, move_id, invoice_id=invoice_id) if self.force_amount_company_currency: if res.get('debit', False): res['debit'] = self.force_amount_company_currency if res.get('credit', False): res['credit'] = self.force_amount_company_currency return res def _get_move_vals(self, journal=None): """If we have a communication on payment group append it before payment communication """ vals = super(AccountPayment, self)._get_move_vals(journal=journal) if self.payment_group_id.communication: vals['ref'] = "%s%s" % (self.payment_group_id.communication, self.communication and ": %s" % self.communication or "") return vals
class InventoryLine(models.Model): _name = "stock.inventory.line" _description = "Inventory Line" _order = "product_name ,inventory_id, location_name, product_code, prodlot_name" inventory_id = fields.Many2one( 'stock.inventory', 'Inventory', index=True, ondelete='cascade') partner_id = fields.Many2one('res.partner', 'Owner') product_id = fields.Many2one( 'product.product', 'Product', index=True, required=True) product_name = fields.Char( 'Product Name', related='product_id.name', store=True) product_code = fields.Char( 'Product Code', related='product_id.default_code', store=True) product_uom_id = fields.Many2one( 'product.uom', 'Product Unit of Measure', required=True, default=lambda self: self.env.ref('product.product_uom_unit', raise_if_not_found=True)) product_qty = fields.Float( 'Checked Quantity', digits=dp.get_precision('Product Unit of Measure'), default=0) location_id = fields.Many2one( 'stock.location', 'Location', index=True, required=True) # TDE FIXME: necessary ? only in order -> replace by location_id location_name = fields.Char( 'Location Name', related='location_id.complete_name', store=True) package_id = fields.Many2one( 'stock.quant.package', 'Pack', index=True) prod_lot_id = fields.Many2one( 'stock.production.lot', 'Lot/Serial Number', domain="[('product_id','=',product_id)]") # TDE FIXME: necessary ? -> replace by location_id prodlot_name = fields.Char( 'Serial Number Name', related='prod_lot_id.name', store=True) company_id = fields.Many2one( 'res.company', 'Company', related='inventory_id.company_id', index=True, readonly=True, store=True) # TDE FIXME: necessary ? -> replace by location_id state = fields.Selection( 'Status', related='inventory_id.state', readonly=True) theoretical_qty = fields.Float( 'Theoretical Quantity', compute='_compute_theoretical_qty', digits=dp.get_precision('Product Unit of Measure'), readonly=True, store=True) @api.one @api.depends('location_id', 'product_id', 'package_id', 'product_uom_id', 'company_id', 'prod_lot_id', 'partner_id') def _compute_theoretical_qty(self): if not self.product_id: self.theoretical_qty = 0 return theoretical_qty = sum([x.qty for x in self._get_quants()]) if theoretical_qty and self.product_uom_id and self.product_id.uom_id != self.product_uom_id: theoretical_qty = self.product_id.uom_id._compute_quantity(theoretical_qty, self.product_uom_id) self.theoretical_qty = theoretical_qty @api.onchange('product_id') def onchange_product(self): res = {} # If no UoM or incorrect UoM put default one from product if self.product_id: self.product_uom_id = self.product_id.uom_id res['domain'] = {'product_uom_id': [('category_id', '=', self.product_id.uom_id.category_id.id)]} return res @api.onchange('product_id', 'location_id', 'product_uom_id', 'prod_lot_id', 'partner_id', 'package_id') def onchange_quantity_context(self): if self.product_id and self.location_id and self.product_id.uom_id.category_id == self.product_uom_id.category_id: # TDE FIXME: last part added because crash self._compute_theoretical_qty() self.product_qty = self.theoretical_qty @api.model def create(self, values): if 'product_id' in values and 'product_uom_id' not in values: values['product_uom_id'] = self.env['product.product'].browse(values['product_id']).uom_id.id existings = self.search([ ('product_id', '=', values.get('product_id')), ('inventory_id.state', '=', 'confirm'), ('location_id', '=', values.get('location_id')), ('partner_id', '=', values.get('partner_id')), ('package_id', '=', values.get('package_id')), ('prod_lot_id', '=', values.get('prod_lot_id'))]) res = super(InventoryLine, self).create(values) if existings: raise UserError(_("You cannot have two inventory adjustements in state 'in Progess' with the same product" "(%s), same location(%s), same package, same owner and same lot. Please first validate" "the first inventory adjustement with this product before creating another one.") % (res.product_id.name, res.location_id.name)) return res def _get_quants(self): return self.env['stock.quant'].search([ ('company_id', '=', self.company_id.id), ('location_id', '=', self.location_id.id), ('lot_id', '=', self.prod_lot_id.id), ('product_id', '=', self.product_id.id), ('owner_id', '=', self.partner_id.id), ('package_id', '=', self.package_id.id)]) def _get_move_values(self, qty, location_id, location_dest_id): self.ensure_one() return { 'name': _('INV:') + (self.inventory_id.name or ''), 'product_id': self.product_id.id, 'product_uom': self.product_uom_id.id, 'product_uom_qty': qty, 'date': self.inventory_id.date, 'company_id': self.inventory_id.company_id.id, 'inventory_id': self.inventory_id.id, 'state': 'confirmed', 'restrict_lot_id': self.prod_lot_id.id, 'restrict_partner_id': self.partner_id.id, 'location_id': location_id, 'location_dest_id': location_dest_id, } def _fixup_negative_quants(self): """ This will handle the irreconciable quants created by a force availability followed by a return. When generating the moves of an inventory line, we look for quants of this line's product created to compensate a force availability. If there are some and if the quant which it is propagated from is still in the same location, we move it to the inventory adjustment location before getting it back. Getting the quantity from the inventory location will allow the negative quant to be compensated. """ self.ensure_one() for quant in self._get_quants().filtered(lambda q: q.propagated_from_id.location_id.id == self.location_id.id): # send the quantity to the inventory adjustment location move_out_vals = self._get_move_values(quant.qty, self.location_id.id, self.product_id.property_stock_inventory.id) move_out = self.env['stock.move'].create(move_out_vals) self.env['stock.quant'].quants_reserve([(quant, quant.qty)], move_out) move_out.action_done() # get back the quantity from the inventory adjustment location move_in_vals = self._get_move_values(quant.qty, self.product_id.property_stock_inventory.id, self.location_id.id) move_in = self.env['stock.move'].create(move_in_vals) move_in.action_done() def _generate_moves(self): moves = self.env['stock.move'] Quant = self.env['stock.quant'] for line in self: line._fixup_negative_quants() if float_utils.float_compare(line.theoretical_qty, line.product_qty, precision_rounding=line.product_id.uom_id.rounding) == 0: continue diff = line.theoretical_qty - line.product_qty if diff < 0: # found more than expected vals = self._get_move_values(abs(diff), line.product_id.property_stock_inventory.id, line.location_id.id) else: vals = self._get_move_values(abs(diff), line.location_id.id, line.product_id.property_stock_inventory.id) move = moves.create(vals) if diff > 0: domain = [('qty', '>', 0.0), ('package_id', '=', line.package_id.id), ('lot_id', '=', line.prod_lot_id.id), ('location_id', '=', line.location_id.id)] preferred_domain_list = [[('reservation_id', '=', False)], [('reservation_id.inventory_id', '!=', line.inventory_id.id)]] quants = Quant.quants_get_preferred_domain(move.product_qty, move, domain=domain, preferred_domain_list=preferred_domain_list) Quant.quants_reserve(quants, move) elif line.package_id: move.action_done() move.quant_ids.write({'package_id': line.package_id.id}) quants = Quant.search([('qty', '<', 0.0), ('product_id', '=', move.product_id.id), ('location_id', '=', move.location_dest_id.id), ('package_id', '!=', False)], limit=1) if quants: for quant in move.quant_ids: if quant.location_id.id == move.location_dest_id.id: #To avoid we take a quant that was reconcile already quant._quant_reconcile_negative(move) return moves