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 process_bank_statement_line(self, st_line_ids, data): """ Handles data sent from the bank statement reconciliation widget (and can otherwise serve as an old-API bridge) :param st_line_ids :param list of dicts data: must contains the keys 'counterpart_aml_dicts', 'payment_aml_ids' and 'new_aml_dicts', whose value is the same as described in process_reconciliation except that ids are used instead of recordsets. """ st_lines = self.env['account.bank.statement.line'].browse(st_line_ids) AccountMoveLine = self.env['account.move.line'] ctx = dict(self._context, force_price_include=False) for st_line, datum in pycompat.izip(st_lines, data): payment_aml_rec = AccountMoveLine.browse(datum.get('payment_aml_ids', [])) for aml_dict in datum.get('counterpart_aml_dicts', []): aml_dict['move_line'] = AccountMoveLine.browse(aml_dict['counterpart_aml_id']) del aml_dict['counterpart_aml_id'] if datum.get('partner_id') is not None: st_line.write({'partner_id': datum['partner_id']}) st_line.with_context(ctx).process_reconciliation( datum.get('counterpart_aml_dicts', []), payment_aml_rec, datum.get('new_aml_dicts', []))
def test_multiple(self): """ With two "concurrent" o2ms, exports the first line combined, then exports the rows for the first o2m, then the rows for the second o2m. """ fields = ['const', 'child1/value', 'child2/value'] child1 = [(0, False, { 'value': v, 'str': 'record%.02d' % index }) for index, v in pycompat.izip(itertools.count(), [4, 42, 36, 4, 13]) ] child2 = [(0, False, { 'value': v, 'str': 'record%.02d' % index }) for index, v in pycompat.izip(itertools.count(10), [8, 12, 8, 55, 33, 13])] self.assertEqual( self.export(child1=child1, child2=False, fields=fields), [ [u'36', u'4', False], ['', u'42', ''], ['', u'36', ''], ['', u'4', ''], ['', u'13', ''], ]) self.assertEqual( self.export(child1=False, child2=child2, fields=fields), [ [u'36', False, u'8'], ['', '', u'12'], ['', '', u'8'], ['', '', u'55'], ['', '', u'33'], ['', '', u'13'], ]) self.assertEqual( self.export(child1=child1, child2=child2, fields=fields), [ [u'36', u'4', u'8'], ['', u'42', ''], ['', u'36', ''], ['', u'4', ''], ['', u'13', ''], ['', '', u'12'], ['', '', u'8'], ['', '', u'55'], ['', '', u'33'], ['', '', u'13'], ])
def iter_tax_codes(self): keys = [c.value for c in self.sheet_tax_codes.row(0)] yield keys for i in range(1, self.sheet_tax_codes.nrows): row = (c.value for c in self.sheet_tax_codes.row(i)) d = OrderedDict(pycompat.izip(keys, row)) d['sign'] = int(d['sign']) d['sequence'] = int(d['sequence']) yield d
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': 'Eagle ERP', '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 _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 _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 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 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 test_read_group_without_name_get(self): model = self.env['test_performance.base'] expected = self.expected_read_group() # use read_group and check the expected result with self.assertQueryCount(__system__=1, demo=1): model.invalidate_cache() result = model.read_group([], ['partner_id', 'value'], ['partner_id']) self.assertEqual(len(result), len(expected)) for res, exp in pycompat.izip(result, expected): self.assertEqual(res['__domain'], exp['__domain']) self.assertEqual(res['partner_id'][0], exp['partner_id'][0]) self.assertEqual(res['partner_id_count'], exp['partner_id_count']) self.assertEqual(res['value'], exp['value']) # now serialize to json, which should force evaluation with self.assertQueryCount(__system__=1, demo=1): json.dumps(result)
def create(self, vals_list): products = super( ProductProduct, self.with_context(create_product_product=True)).create(vals_list) for product, vals in pycompat.izip(products, vals_list): # When a unique variant is created from tmpl then the standard price is set by _set_standard_price if not (self.env.context.get('create_from_tmpl') and len(product.product_tmpl_id.product_variant_ids) == 1): product._set_standard_price(vals.get('standard_price') or 0.0) # `_get_variant_id_for_combination` depends on existing variants self.clear_caches() self.env['product.template'].invalidate_cache( fnames=[ 'valid_archived_variant_ids', 'valid_existing_variant_ids', 'product_variant_ids', 'product_variant_id', 'product_variant_count' ], ids=products.mapped('product_tmpl_id').ids) return products
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 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 _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 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() if filename_obj == 'factur-x.xml': try: tree = etree.fromstring(content) except: continue self._import_facturx_invoice(tree) buffer.close() return res except: # Malformed PDF. pass buffer.close() return res
def iter_fiscal_pos_map(self): keys = [c.value for c in self.sheet_fiscal_pos_map.row(0)] yield keys for i in range(1, self.sheet_fiscal_pos_map.nrows): row = (c.value for c in self.sheet_fiscal_pos_map.row(i)) yield OrderedDict(pycompat.izip(keys, row))
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))
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 iter_taxes(self): keys = [c.value for c in self.sheet_taxes.row(0)] yield keys for i in range(1, self.sheet_taxes.nrows): row = (c.value for c in self.sheet_taxes.row(i)) yield OrderedDict(pycompat.izip(keys, row))
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 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_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 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, }) def compute_tax(product, price, qty=1, taxes=None): if not taxes: taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.user.id) currency = self.pos_config.pricelist_id.currency_id res = taxes.compute_all(price, currency, qty, product=product) untax = res['total_excluded'] return untax, sum(tax.get('amount', 0.0) for tax in res['taxes']) # 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 = compute_tax(self.product3, 450*0.95, 2) untax2, atax2 = 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)