def _compute_display_name(self): self2 = self.with_context(display_website=False) super(Partner, self2)._compute_display_name() # onchange uses the cache to retrieve value, we need to copy computed_value into the initial env for record, record2 in izip(self, self2): record.display_name = record2.display_name
def load_information_from_description_file(module, mod_path=None): """ :param module: The name of the module (sale, purchase, ...) :param mod_path: Physical path of module, if not providedThe name of the module (sale, purchase, ...) """ if not mod_path: mod_path = get_module_path(module, downloaded=True) manifest_file = module_manifest(mod_path) if manifest_file: # default values for descriptor info = { 'application': False, 'author': 'Metric SW', 'auto_install': False, 'category': 'Uncategorized', 'depends': [], 'description': '', 'icon': get_module_icon(module), 'installable': True, 'license': 'LGPL-3', 'post_load': None, 'version': '1.0', 'web': False, 'sequence': 100, 'summary': '', 'website': '', } info.update( pycompat.izip( 'depends data demo test init_xml update_xml demo_xml'.split(), iter(list, None))) f = tools.file_open(manifest_file, mode='rb') try: info.update(ast.literal_eval(pycompat.to_native(f.read()))) finally: f.close() if not info.get('description'): readme_path = [ opj(mod_path, x) for x in README if os.path.isfile(opj(mod_path, x)) ] if readme_path: readme_text = tools.file_open(readme_path[0]).read() info['description'] = readme_text if 'active' in info: # 'active' has been renamed 'auto_install' info['auto_install'] = info['active'] info['version'] = adapt_version(info['version']) return info _logger.debug('module %s: no manifest file found %s', module, MANIFEST_NAMES) return {}
def chk(lst, verbose=False): pvs = [] for v in lst: pv = parse_version(v) pvs.append(pv) if verbose: print(v, pv) for a, b in pycompat.izip(pvs, pvs[1:]): assert a < b, '%s < %s == %s' % (a, b, a < b)
def message_post(self, **kwargs): # OVERRIDE # /!\ 'default_res_id' in self._context is used to don't process attachment when using a form view. res = super(AccountInvoice, self).message_post(**kwargs) def _get_attachment_filename(attachment): # Handle both _Attachment namedtuple in mail.thread or ir.attachment. return hasattr(attachment, 'fname') and getattr(attachment, 'fname') or attachment.name def _get_attachment_content(attachment): # Handle both _Attachment namedtuple in mail.thread or ir.attachment. return hasattr(attachment, 'content') and getattr(attachment, 'content') or base64.b64decode(attachment.datas) if 'default_res_id' not in self._context and len(self) == 1 and self.state == 'draft' and self.type in ('in_invoice', 'in_refund'): # Get attachments. # - 'attachments' is a namedtuple defined in mail.thread looking like: # _Attachment = namedtuple('Attachment', ('fname', 'content', 'info')) # - 'attachment_ids' is a list of ir.attachment records ids. attachments = kwargs.get('attachments', []) if kwargs.get('attachment_ids'): attachments += self.env['ir.attachment'].browse(kwargs['attachment_ids']) for attachment in attachments: filename = _get_attachment_filename(attachment) content = _get_attachment_content(attachment) # Check if the attachment is a pdf. if not filename.endswith('.pdf'): continue buffer = io.BytesIO(content) try: reader = PdfFileReader(buffer) # Search for Factur-x embedded file. if reader.trailer['/Root'].get('/Names') and reader.trailer['/Root']['/Names'].get('/EmbeddedFiles'): # N.B: embedded_files looks like: # ['file.xml', {'/Type': '/Filespec', '/F': 'file.xml', '/EF': {'/F': IndirectObject(22, 0)}}] embedded_files = reader.trailer['/Root']['/Names']['/EmbeddedFiles']['/Names'] # '[::2]' because it's a list [fn_1, content_1, fn_2, content_2, ..., fn_n, content_2] for filename_obj, content_obj in list(pycompat.izip(embedded_files, embedded_files[1:]))[::2]: content = content_obj.getObject()['/EF']['/F'].getData() try: tree = etree.fromstring(content) except: continue if tree.tag == '{urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100}CrossIndustryInvoice': self._import_facturx_invoice(tree) buffer.close() return res except: # Malformed PDF. pass buffer.close() return res
def _load_records_create(self, vals_list): partners = super(Partner, self.with_context(_partners_skip_fields_sync=True) )._load_records_create(vals_list) # batch up first part of _fields_sync # group partners by commercial_partner_id (if not self) and parent_id (if type == contact) groups = collections.defaultdict(list) for partner, vals in pycompat.izip(partners, vals_list): cp_id = None if vals.get( 'parent_id') and partner.commercial_partner_id != partner: cp_id = partner.commercial_partner_id.id add_id = None if partner.parent_id and partner.type == 'contact': add_id = partner.parent_id.id groups[(cp_id, add_id)].append(partner.id) for (cp_id, add_id), children in groups.items(): # values from parents (commercial, regular) written to their common children to_write = {} # commercial fields from commercial partner if cp_id: to_write = self.browse(cp_id)._update_fields_values( self._commercial_fields()) # address fields from parent if add_id: parent = self.browse(add_id) for f in self._address_fields(): v = parent[f] if v: to_write[f] = v.id if isinstance( v, models.BaseModel) else v if to_write: self.browse(children).write(to_write) # do the second half of _fields_sync the "normal" way for partner, vals in pycompat.izip(partners, vals_list): partner._children_sync(vals) partner._handle_first_contact_creation() return partners
def get_products_price(self, products, quantities, partners, date=False, uom_id=False): """ For a given pricelist, return price for products Returns: dict{product_id: product price}, in the given pricelist """ self.ensure_one() return { product_id: res_tuple[0] for product_id, res_tuple in self._compute_price_rule( list(pycompat.izip(products, quantities, partners)), date=date, uom_id=uom_id).items() }
def _are_archs_equal(self, arch1, arch2): # Note that comparing the strings would not be ok as attributes order # must not be relevant if arch1.tag != arch2.tag: return False if arch1.text != arch2.text: return False if arch1.tail != arch2.tail: return False if arch1.attrib != arch2.attrib: return False if len(arch1) != len(arch2): return False return all( self._are_archs_equal(arch1, arch2) for arch1, arch2 in pycompat.izip(arch1, arch2))
def create_view(self, archf, terms, **kwargs): view = self.env['ir.ui.view'].create({ 'name': 'test', 'model': 'res.partner', 'arch': archf % terms, }) for lang, trans_terms in kwargs.items(): for src, val in pycompat.izip(terms, trans_terms): self.env['ir.translation'].create({ 'type': 'model_terms', 'name': 'ir.ui.view,arch_db', 'lang': lang, 'res_id': view.id, 'src': src, 'value': val, 'state': 'translated', }) return view
def fetch_dashboard_data(self, website_id, date_from, date_to): Website = request.env['website'] has_group_system = request.env.user.has_group('base.group_system') has_group_designer = request.env.user.has_group( 'website.group_website_designer') dashboard_data = { 'groups': { 'system': has_group_system, 'website_designer': has_group_designer }, 'currency': request.env.user.company_id.currency_id.id, 'dashboards': { 'visits': {}, } } current_website = website_id and Website.browse( website_id) or Website.get_current_website() multi_website = request.env.user.has_group( 'website.group_multi_website') websites = multi_website and request.env['website'].search( []) or current_website dashboard_data['websites'] = websites.read(['id', 'name']) for rec, website in pycompat.izip(websites, dashboard_data['websites']): website['domain'] = rec._get_http_domain() if website['id'] == current_website.id: website['selected'] = True if has_group_designer: if current_website.google_management_client_id and current_website.google_analytics_key: dashboard_data['dashboards']['visits'] = dict( ga_client_id=current_website.google_management_client_id or '', ga_analytics_key=current_website.google_analytics_key or '', ) return dashboard_data
def test_invoice_with_discount(self): """ Test invoice with a discount and check discount applied on both SO lines and an invoice lines """ # Update discount and delivered quantity on SO lines self.sol_prod_order.write({'discount': 20.0}) self.sol_serv_deliver.write({'discount': 20.0, 'qty_delivered': 4.0}) self.sol_serv_order.write({'discount': -10.0}) self.sol_prod_deliver.write({'qty_delivered': 2.0}) for line in self.sale_order.order_line.filtered(lambda l: l.discount): product_price = line.price_unit * line.product_uom_qty self.assertEquals(line.discount, (product_price - line.price_subtotal) / product_price * 100, 'Discount should be applied on order line') # lines are in draft for line in self.sale_order.order_line: self.assertTrue(float_is_zero(line.untaxed_amount_to_invoice, precision_digits=2), "The amount to invoice should be zero, as the line is in draf state") self.assertTrue(float_is_zero(line.untaxed_amount_invoiced, precision_digits=2), "The invoiced amount should be zero, as the line is in draft state") self.sale_order.action_confirm() for line in self.sale_order.order_line: self.assertTrue(float_is_zero(line.untaxed_amount_invoiced, precision_digits=2), "The invoiced amount should be zero, as the line is in draft state") self.assertEquals(self.sol_serv_order.untaxed_amount_to_invoice, 297, "The untaxed amount to invoice is wrong") self.assertEquals(self.sol_serv_deliver.untaxed_amount_to_invoice, self.sol_serv_deliver.qty_delivered * self.sol_serv_deliver.price_reduce, "The untaxed amount to invoice should be qty deli * price reduce, so 4 * (180 - 36)") self.assertEquals(self.sol_prod_deliver.untaxed_amount_to_invoice, 140, "The untaxed amount to invoice should be qty deli * price reduce, so 4 * (180 - 36)") # Let's do an invoice with invoiceable lines payment = self.env['sale.advance.payment.inv'].with_context(self.context).create({ 'advance_payment_method': 'delivered' }) payment.create_invoices() invoice = self.sale_order.invoice_ids[0] invoice.action_invoice_open() # Check discount appeared on both SO lines and invoice lines for line, inv_line in pycompat.izip(self.sale_order.order_line, invoice.invoice_line_ids): self.assertEquals(line.discount, inv_line.discount, 'Discount on lines of order and invoice should be same')
def create(self, vals_list): if self.env.context.get('import_file'): self._check_import_consistency(vals_list) for vals in vals_list: if vals.get('website'): vals['website'] = self._clean_website(vals['website']) if vals.get('parent_id'): vals['company_name'] = False # compute default image in create, because computing gravatar in the onchange # cannot be easily performed if default images are in the way if not vals.get('image'): vals['image'] = self._get_default_image( vals.get('type'), vals.get('is_company'), vals.get('parent_id')) tools.image_resize_images(vals, sizes={'image': (1024, None)}) partners = super(Partner, self).create(vals_list) if self.env.context.get('_partners_skip_fields_sync'): return partners for partner, vals in pycompat.izip(partners, vals_list): partner._fields_sync(vals) partner._handle_first_contact_creation() return partners
def _create_all_specific_views(self, processed_modules): """ When creating a generic child view, we should also create that view under specific view trees (COW'd). Top level view (no inherit_id) do not need that behavior as they will be shared between websites since there is no specific yet. """ # Only for the modules being processed regex = '^(%s)[.]' % '|'.join(processed_modules) # Retrieve the views through a SQl query to avoid ORM queries inside of for loop # Retrieves all the views that are missing their specific counterpart with all the # specific view parent id and their website id in one query query = """ SELECT generic.id, ARRAY[array_agg(spec_parent.id), array_agg(spec_parent.website_id)] FROM ir_ui_view generic INNER JOIN ir_ui_view generic_parent ON generic_parent.id = generic.inherit_id INNER JOIN ir_ui_view spec_parent ON spec_parent.key = generic_parent.key LEFT JOIN ir_ui_view specific ON specific.key = generic.key AND specific.website_id = spec_parent.website_id WHERE generic.type='qweb' AND generic.website_id IS NULL AND generic.key ~ %s AND spec_parent.website_id IS NOT NULL AND specific.id IS NULL GROUP BY generic.id """ self.env.cr.execute(query, (regex, )) result = dict(self.env.cr.fetchall()) for record in self.browse(result.keys()): specific_parent_view_ids, website_ids = result[record.id] for specific_parent_view_id, website_id in pycompat.izip( specific_parent_view_ids, website_ids): record.with_context(website_id=website_id).write({ 'inherit_id': specific_parent_view_id, }) super(View, self)._create_all_specific_views(processed_modules)
def update(self, records, field, values): """ Set the values of ``field`` for several ``records``. """ key = records.env.cache_key(field) self._data[key][field].update(pycompat.izip(records._ids, values))
def _compute_params(self): self_bin = self.with_context(bin_size=False, bin_size_params_store=False) for record, record_bin in pycompat.izip(self, self_bin): record.params = record_bin.params_store and safe_eval( record_bin.params_store, {'uid': self._uid})
def _price_get_multi(self, pricelist, products_by_qty_by_partner): """ Mono pricelist, multi product - return price per product """ return pricelist.get_products_price( list(pycompat.izip(**products_by_qty_by_partner)))
def test_create_multi(self): """ create for multiple records """ # assumption: 'res.bank' does not override 'create' vals_list = [{'name': name} for name in ('Foo', 'Bar', 'Baz')] vals_list[0]['email'] = '*****@*****.**' for vals in vals_list: record = self.env['res.bank'].create(vals) self.assertEqual(len(record), 1) self.assertEqual(record.name, vals['name']) self.assertEqual(record.email, vals.get('email', False)) records = self.env['res.bank'].create([]) self.assertFalse(records) records = self.env['res.bank'].create(vals_list) self.assertEqual(len(records), len(vals_list)) for record, vals in pycompat.izip(records, vals_list): self.assertEqual(record.name, vals['name']) self.assertEqual(record.email, vals.get('email', False)) # create countries and states vals_list = [{ 'name': 'Foo', 'state_ids': [ (0, 0, { 'name': 'North Foo', 'code': 'NF' }), (0, 0, { 'name': 'South Foo', 'code': 'SF' }), (0, 0, { 'name': 'West Foo', 'code': 'WF' }), (0, 0, { 'name': 'East Foo', 'code': 'EF' }), ], }, { 'name': 'Bar', 'state_ids': [ (0, 0, { 'name': 'North Bar', 'code': 'NB' }), (0, 0, { 'name': 'South Bar', 'code': 'SB' }), ], }] foo, bar = self.env['res.country'].create(vals_list) self.assertEqual(foo.name, 'Foo') self.assertCountEqual(foo.mapped('state_ids.code'), ['NF', 'SF', 'WF', 'EF']) self.assertEqual(bar.name, 'Bar') self.assertCountEqual(bar.mapped('state_ids.code'), ['NB', 'SB'])
def create(self, vals_list): for vals in vals_list: # If the move line is directly create on the picking view. # If this picking is already done we should generate an # associated done move. if 'picking_id' in vals and not vals.get('move_id'): picking = self.env['stock.picking'].browse(vals['picking_id']) if picking.state == 'done': product = self.env['product.product'].browse( vals['product_id']) new_move = self.env['stock.move'].create({ 'name': _('New Move:') + product.display_name, 'product_id': product.id, 'product_uom_qty': 'qty_done' in vals and vals['qty_done'] or 0, 'product_uom': vals['product_uom_id'], 'location_id': 'location_id' in vals and vals['location_id'] or picking.location_id.id, 'location_dest_id': 'location_dest_id' in vals and vals['location_dest_id'] or picking.location_dest_id.id, 'state': 'done', 'additional': True, 'picking_id': picking.id, }) vals['move_id'] = new_move.id mls = super(StockMoveLine, self).create(vals_list) for ml, vals in izip(mls, vals_list): if ml.state == 'done': if 'qty_done' in vals: ml.move_id.product_uom_qty = ml.move_id.quantity_done if ml.product_id.type == 'product': Quant = self.env['stock.quant'] quantity = ml.product_uom_id._compute_quantity( ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP') in_date = None available_qty, in_date = Quant._update_available_quantity( ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id) if available_qty < 0 and ml.lot_id: # see if we can compensate the negative quants with some untracked quants untracked_qty = Quant._get_available_quantity( ml.product_id, ml.location_id, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) if untracked_qty: taken_from_untracked_qty = min( untracked_qty, abs(quantity)) Quant._update_available_quantity( ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id) Quant._update_available_quantity( ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id) Quant._update_available_quantity( ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date) next_moves = ml.move_id.move_dest_ids.filtered( lambda move: move.state not in ('done', 'cancel')) next_moves._do_unreserve() next_moves._action_assign() return mls
errors = 0 def try_round(amount, expected, precision_digits=3): global count, errors; count += 1 result = float_repr(float_round(amount, precision_digits=precision_digits), precision_digits=precision_digits) if result != expected: errors += 1 print('###!!! Rounding error: got %s , expected %s' % (result, expected)) # Extended float range test, inspired by Cloves Almeida's test on bug #882036. fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555] expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556'] precisions = [2, 2, 2, 2, 2, 2, 3, 4] for magnitude in range(7): for frac, exp, prec in pycompat.izip(fractions, expecteds, precisions): for sign in [-1,1]: for x in range(0, 10000, 97): n = x * 10**magnitude f = sign * (n + frac) f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp try_round(f, f_exp, precision_digits=prec) stop = time.time() # Micro-bench results: # 47130 round calls in 0.422306060791 secs, with Python 2.6.7 on Core i3 x64 # with decimal: # 47130 round calls in 6.612248100021 secs, with Python 2.6.7 on Core i3 x64 print(count, " round calls, ", errors, "errors, done in ", (stop-start), 'secs')
def fast_counterpart_creation(self): """This function is called when confirming a bank statement and will allow to automatically process lines without going in the bank reconciliation widget. By setting an account_id on bank statement lines, it will create a journal entry using that account to counterpart the bank account """ payment_list = [] move_list = [] account_type_receivable = self.env.ref( 'account.data_account_type_receivable') already_done_stmt_line_ids = [ a['statement_line_id'][0] for a in self.env['account.move.line'].read_group([( 'statement_line_id', 'in', self.ids)], ['statement_line_id'], ['statement_line_id']) ] managed_st_line = [] for st_line in self: # Technical functionality to automatically reconcile by creating a new move line if st_line.account_id and not st_line.id in already_done_stmt_line_ids: managed_st_line.append(st_line.id) # Create payment vals total = st_line.amount payment_methods = ( total > 0 ) and st_line.journal_id.inbound_payment_method_ids or st_line.journal_id.outbound_payment_method_ids currency = st_line.journal_id.currency_id or st_line.company_id.currency_id partner_type = 'customer' if st_line.account_id.user_type_id == account_type_receivable else 'supplier' payment_list.append({ 'payment_method_id': payment_methods and payment_methods[0].id or False, 'payment_type': total > 0 and 'inbound' or 'outbound', 'partner_id': st_line.partner_id.id, 'partner_type': partner_type, 'journal_id': st_line.statement_id.journal_id.id, 'payment_date': st_line.date, 'state': 'reconciled', 'currency_id': currency.id, 'amount': abs(total), 'communication': st_line._get_communication( payment_methods[0] if payment_methods else False), 'name': st_line.statement_id.name or _("Bank Statement %s") % st_line.date, }) # Create move and move line vals move_vals = st_line._prepare_reconciliation_move( st_line.statement_id.name) aml_dict = { 'name': st_line.name, 'debit': st_line.amount < 0 and -st_line.amount or 0.0, 'credit': st_line.amount > 0 and st_line.amount or 0.0, 'account_id': st_line.account_id.id, 'partner_id': st_line.partner_id.id, 'statement_line_id': st_line.id, } st_line._prepare_move_line_for_currency( aml_dict, st_line.date or fields.Date.context_today()) move_vals['line_ids'] = [(0, 0, aml_dict)] balance_line = self._prepare_reconciliation_move_line( move_vals, -aml_dict['debit'] if st_line.amount < 0 else aml_dict['credit']) move_vals['line_ids'].append((0, 0, balance_line)) move_list.append(move_vals) # Creates payment_ids = self.env['account.payment'].create(payment_list) for payment_id, move_vals in pycompat.izip(payment_ids, move_list): for line in move_vals['line_ids']: line[2]['payment_id'] = payment_id.id move_ids = self.env['account.move'].create(move_list) move_ids.post() for move, st_line, payment in pycompat.izip( move_ids, self.browse(managed_st_line), payment_ids): st_line.write({'move_name': move.name}) payment.write({'payment_reference': move.name})
def test_rounding_03(self): """ Test rounding methods with 3 digits. """ def try_round(amount, expected, digits=3, method='HALF-UP'): value = float_round(amount, precision_digits=digits, rounding_method=method) result = float_repr(value, precision_digits=digits) self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected)) try_round(2.6745, '2.675') try_round(-2.6745, '-2.675') try_round(2.6744, '2.674') try_round(-2.6744, '-2.674') try_round(0.0004, '0.000') try_round(-0.0004, '-0.000') try_round(357.4555, '357.456') try_round(-357.4555, '-357.456') try_round(457.4554, '457.455') try_round(-457.4554, '-457.455') # Try some rounding value with rounding method UP instead of HALF-UP # We use 8.175 because when normalizing 8.175 with precision_digits=3 it gives # us 8175,0000000001234 as value, and if not handle correctly the rounding UP # value will be incorrect (should be 8,175 and not 8,176) try_round(8.175, '8.175', method='UP') try_round(8.1751, '8.176', method='UP') try_round(-8.175, '-8.175', method='UP') try_round(-8.1751, '-8.176', method='UP') try_round(-6.000, '-6.000', method='UP') try_round(1.8, '2', 0, method='UP') try_round(-1.8, '-2', 0, method='UP') # Try some rounding value with rounding method DOWN instead of HALF-UP # We use 2.425 because when normalizing 2.425 with precision_digits=3 it gives # us 2424.9999999999995 as value, and if not handle correctly the rounding DOWN # value will be incorrect (should be 2.425 and not 2.424) try_round(2.425, '2.425', method='DOWN') try_round(2.4249, '2.424', method='DOWN') try_round(-2.425, '-2.425', method='DOWN') try_round(-2.4249, '-2.424', method='DOWN') try_round(-2.500, '-2.500', method='DOWN') try_round(1.8, '1', 0, method='DOWN') try_round(-1.8, '-1', 0, method='DOWN') # Extended float range test, inspired by Cloves Almeida's test on bug #882036. fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555] expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556'] precisions = [2, 2, 2, 2, 2, 2, 3, 4] # Note: max precision for double floats is 53 bits of precision or # 17 significant decimal digits for magnitude in range(7): for frac, exp, prec in pycompat.izip(fractions, expecteds, precisions): for sign in [-1,1]: for x in range(0, 10000, 97): n = x * 10 ** magnitude f = sign * (n + frac) f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp try_round(f, f_exp, digits=prec) def try_zero(amount, expected): self.assertEqual(float_is_zero(amount, precision_digits=3), expected, "Rounding error: %s should be zero!" % amount) try_zero(0.0002, True) try_zero(-0.0002, True) try_zero(0.00034, True) try_zero(0.0005, False) try_zero(-0.0005, False) try_zero(0.0008, False) try_zero(-0.0008, False) def try_compare(amount1, amount2, expected): self.assertEqual(float_compare(amount1, amount2, precision_digits=3), expected, "Rounding error, compare_amounts(%s,%s) should be %s" % (amount1, amount2, expected)) try_compare(0.0003, 0.0004, 0) try_compare(-0.0003, -0.0004, 0) try_compare(0.0002, 0.0005, -1) try_compare(-0.0002, -0.0005, 1) try_compare(0.0009, 0.0004, 1) try_compare(-0.0009, -0.0004, -1) try_compare(557.4555, 557.4556, 0) try_compare(-557.4555, -557.4556, 0) try_compare(657.4444, 657.445, -1) try_compare(-657.4444, -657.445, 1) # Rounding to unusual rounding units (e.g. coin values) def try_round(amount, expected, precision_rounding=None, method='HALF-UP'): value = float_round(amount, precision_rounding=precision_rounding, rounding_method=method) result = float_repr(value, precision_digits=2) self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected)) try_round(-457.4554, '-457.45', precision_rounding=0.05) try_round(457.444, '457.50', precision_rounding=0.5) try_round(457.3, '455.00', precision_rounding=5) try_round(457.5, '460.00', precision_rounding=5) try_round(457.1, '456.00', precision_rounding=3) try_round(2.5, '2.50', precision_rounding=0.05, method='DOWN') try_round(-2.5, '-2.50', precision_rounding=0.05, method='DOWN')
def test_order_to_payment_currency(self): """ In order to test the Point of Sale in module, I will do a full flow from the sale to the payment and invoicing. I will use two products, one with price including a 10% tax, the other one with 5% tax excluded from the price. The order will be in a different currency than the company currency. """ # Make sure the company is in USD self.env.cr.execute( "UPDATE res_company SET currency_id = %s WHERE id = %s", [self.env.ref('base.USD').id, self.env.user.company_id.id]) # Demo data are crappy, clean-up the rates self.env['res.currency.rate'].search([]).unlink() self.env['res.currency.rate'].create({ 'name': '2010-01-01', 'rate': 2.0, 'currency_id': self.env.ref('base.EUR').id, }) # I click on create a new session button self.pos_config.open_session_cb() # I create a PoS order with 2 units of PCSC234 at 450 EUR (Tax Incl) # and 3 units of PCSC349 at 300 EUR. (Tax Excl) untax1, atax1 = self.compute_tax(self.product3, 450*0.95, 2) untax2, atax2 = self.compute_tax(self.product4, 300*0.95, 3) self.pos_order_pos0 = self.PosOrder.create({ 'company_id': self.company_id, 'pricelist_id': self.partner1.property_product_pricelist.copy(default={'currency_id': self.env.ref('base.EUR').id}).id, 'partner_id': self.partner1.id, 'lines': [(0, 0, { 'name': "OL/0001", 'product_id': self.product3.id, 'price_unit': 450, 'discount': 0.0, 'qty': 2.0, 'tax_ids': [(6, 0, self.product3.taxes_id.ids)], 'price_subtotal': untax1, 'price_subtotal_incl': untax1 + atax1, }), (0, 0, { 'name': "OL/0002", 'product_id': self.product4.id, 'price_unit': 300, 'discount': 0.0, 'qty': 3.0, 'tax_ids': [(6, 0, self.product4.taxes_id.ids)], 'price_subtotal': untax2, 'price_subtotal_incl': untax2 + atax2, })], 'amount_tax': atax1 + atax2, 'amount_total': untax1 + untax2 + atax1 + atax2, 'amount_paid': 0.0, 'amount_return': 0.0, }) # I check that the total of the order is now equal to (450*2 + # 300*3*1.05)*0.95 self.assertLess( abs(self.pos_order_pos0.amount_total - (450 * 2 + 300 * 3 * 1.05) * 0.95), 0.01, 'The order has a wrong total including tax and discounts') # I click on the "Make Payment" wizard to pay the PoS order with a # partial amount of 100.0 EUR context_make_payment = {"active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id} self.pos_make_payment_0 = self.PosMakePayment.with_context(context_make_payment).create({ 'amount': 100.0 }) # I click on the validate button to register the payment. context_payment = {'active_id': self.pos_order_pos0.id} self.pos_make_payment_0.with_context(context_payment).check() # I check that the order is not marked as paid yet self.assertEqual(self.pos_order_pos0.state, 'draft', 'Order should be in draft state.') # On the second payment proposition, I check that it proposes me the # remaining balance which is 1790.0 EUR defs = self.pos_make_payment_0.with_context({'active_id': self.pos_order_pos0.id}).default_get(['amount']) self.assertLess( abs(defs['amount'] - ((450 * 2 + 300 * 3 * 1.05) * 0.95 - 100.0)), 0.01, "The remaining balance is incorrect.") #'I pay the remaining balance. context_make_payment = { "active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id} self.pos_make_payment_1 = self.PosMakePayment.with_context(context_make_payment).create({ 'amount': (450 * 2 + 300 * 3 * 1.05) * 0.95 - 100.0 }) # I click on the validate button to register the payment. self.pos_make_payment_1.with_context(context_make_payment).check() # I check that the order is marked as paid self.assertEqual(self.pos_order_pos0.state, 'paid', 'Order should be in paid state.') # I generate the journal entries self.pos_order_pos0._create_account_move_line() # I test that the generated journal entry is attached to the PoS order self.assertTrue(self.pos_order_pos0.account_move, "Journal entry has not been attached to Pos order.") # Check the amounts debit_lines = self.pos_order_pos0.account_move.mapped('line_ids.debit') credit_lines = self.pos_order_pos0.account_move.mapped('line_ids.credit') amount_currency_lines = self.pos_order_pos0.account_move.mapped('line_ids.amount_currency') for a, b in pycompat.izip(sorted(debit_lines), [0.0, 0.0, 0.0, 0.0, 879.55]): self.assertAlmostEqual(a, b) for a, b in pycompat.izip(sorted(credit_lines), [0.0, 22.5, 40.91, 388.64, 427.5]): self.assertAlmostEqual(a, b) for a, b in pycompat.izip(sorted(amount_currency_lines), [-855.0, -777.27, -81.82, -45.0, 1752.75]): self.assertAlmostEqual(a, b)
def _compute_signup_valid(self): dt = now() for partner, partner_sudo in pycompat.izip(self, self.sudo()): partner.signup_valid = bool(partner_sudo.signup_token) and \ (not partner_sudo.signup_expiration or dt <= partner_sudo.signup_expiration)