def bank_reconciliation(request, account): template_name = 'subledgers/bank_reconciliations/banktransaction_list.html' context_data = { 'object_list': BankLine.objects.unreconciled(), 'bank_account': Account.objects.by_code(account), 'account_list': Account.objects.regular().order_by('element', 'number') } if request.method == 'POST': form = BankReconciliationForm(request.POST) context_data['success'] = request.POST if form.is_valid(): transaction_kwargs = { 'user': request.user, 'date': str(form.cleaned_data.get('date')), 'source': get_source(BankLine), 'value': form.cleaned_data.get('value'), 'account_DR': account, 'account_CR': form.cleaned_data.get('account'), } this = BankLine.objects.get(pk=form.cleaned_data.get('pk')) this.save_transaction(transaction_kwargs) context_data['success'] = "{} {}".format(this, this.transaction) return render(request, template_name, context_data)
def process_kwargs(self, kwargs): """ Minimum keys assumed included: 'user', 'date', 'lines' 1. Keys added (if not already defined): 'cls', 'source' add `source` using ledgers.utils.get_source(`Model`) based upon `Model` provided in object settings 2. Keys checked: 'relation', dates, decimals, required fields IS_DATE using dateparser.parse IS_DECIMAL using ledgers.utils.make_decimal() IS_RELATION to normalise relation field name 3. Create dicts: (obj_kwargs, trans_kwargs) Check all required fields are represented (or explode) append `row_dict` set to `list_kwargs` """ # Generate list of codes to check against. # Cheaper than checking db for every account. ACCOUNT_CODE_LIST = Account.get_account_code_list() process_kwargs = {k.lower(): v for k, v in kwargs.items()} # If `cls` not manually described in kwargs. process_kwargs['source'] = utils.get_source(self) for key in kwargs: if key in settings.FIELD_IS_DATE: process_kwargs[key] = utils.make_date(kwargs[key]) if key in settings.FIELD_IS_DECIMAL: process_kwargs[key] = utils.make_decimal(kwargs[key]) if key in settings.FIELD_IS_RELATION: # Relation names are not always consistently used. # eg. Creditor, Relation if kwargs[key] is None: # Is likely to have emtpy relation column heading. # Remove empty relation, so doesn't blow up save. process_kwargs.pop(key) else: process_kwargs['relation'] = self.get_relation(kwargs[key]) if key in ACCOUNT_CODE_LIST: process_kwargs.setdefault('accounts', []).append( (key, kwargs[key])) self.check_required(process_kwargs) return process_kwargs
def test_journalentry_save_transaction_account_code_passes(self): new_journalentry = JournalEntry() new_journalentry.save_transaction(self.kwargs) test_kwargs = { 'transaction__date': utils.make_date('5-May-2020'), 'transaction__user': self.user, 'transaction__value': 1.00, 'transaction__source': utils.get_source(JournalEntry) } test_object = JournalEntry.objects.get(**test_kwargs) self.assertEqual(new_journalentry, test_object)
def test_creditorinvoice_save_transaction_lines_passes(self): self.kwargs = { 'date': '5-May-2020', 'user': self.user, 'lines': [(self.a1, 1), (self.a2, 2), (self.c, -3)], } self.kwargs['invoice_number'] = 'abc123' self.kwargs['relation'] = self.creditor self.kwargs['gst_total'] = 0 new_creditorinvoice = CreditorInvoice() new_creditorinvoice.save_transaction(self.kwargs) test_kwargs = { 'transaction__date': utils.make_date('5-May-2020'), 'transaction__user': self.user, 'transaction__value': 3.00, 'transaction__source': utils.get_source(CreditorInvoice) } test_object = CreditorInvoice.objects.get(**test_kwargs) self.assertEqual(new_creditorinvoice, test_object)
def bank_categorisation(request): template_name = "subledgers/bank_reconciliations/bank_categorisation.html" context_data = { 'object_list': BankLine.objects.unreconciled(), 'subledger_list': SUBLEDGERS_AVAILABLE, } # @@TOOD: figure out how to display already sorted. if request.method == 'POST': context_data['request'] = request.POST subledger = request.POST['subledger'] log = [] for field in request.POST: if field.split('-')[0] == 'catpk': # @@TODO make this a method of some kind. pk = field.split('-')[1] bank_line = BankLine.objects.get(pk=pk) if request.POST['manual-account']: account = request.POST['manual-account'] else: account = SUBLEDGERS_AVAILABLE[subledger]['account'] transaction_kwargs = { 'user': request.user, 'date': bank_line.date, 'source': get_source(BankEntry), 'value': bank_line.value, 'account_DR': account, 'account_CR': bank_line.bank_account.account } bank_entry = BankEntry(bank_line=bank_line, subledger=subledger) bank_entry.save_transaction(transaction_kwargs) log.append(bank_entry) context_data['test'] = log return render(request, template_name, context_data)
def test_get_source_str_passes(self): test_input = 'ledgers.models.Account' test_result = "ledgers.models.Account" self.assertEqual(utils.get_source(test_input), test_result)
def test_get_source_Obj_object_passes(self): Account.objects.create(element="01", number="0001") test_input = Account.objects.first() test_result = "ledgers.models.Account" self.assertEqual(utils.get_source(test_input), test_result)
def test_get_source_Obj_instance_passes(self): test_input = Account() test_result = "ledgers.models.Account" self.assertEqual(utils.get_source(test_input), test_result)
def convert_import_to_objects(dump, user, object_name=None, live=True): """ ** End-to-End ** Main function for bringing together the elements of constructing the list of kwargs for transactions / invoices based upon whatever is required for the object. Defining Object type by "object_name": Either: batch of same Object Class(using * arg `object_name`) or: by calling method from Object Class(eg. CreditorInvoice) or: column header `object_name` defining on a row-by-row basis. There is the choice of either preselecting what object_name of objects are being defined such as "CreditorInvoice" objects or "JournalEntry" objects. Alternatively each `row` should define a `object_name`. `user` will be added here as the individual creating this list is the one who should be tied to the objects. """ # ** Part 1. convert dump to `list` of `dict` objects table = utils.tsv_to_dict(dump) obj_list, valid_kwargs = [], [] for row_dict in table: # Copy kwargs for safety to ensure valid set/uniform keys kwargs = {k.lower(): v for k, v in row_dict.items()} kwargs['user'] = user """ There are a couple of different ways to provide `cls`. (a) `type` column provided for row in csv (b) `object_name` arg provided Alternatively done at the single object level not in scope of this function. Entry "make/create" methods employed below are used instead. """ # `type` is particular to import as easy nomenclature for csv # 1. prioritise type provided for individual line if kwargs.get('type'): cls = utils.get_cls(kwargs['type']) # 2. fall back on `object_name` arg provided elif object_name: cls = utils.get_cls(object_name) else: raise Exception( "No `type` column specified for {}, unable to create objects.".format(kwargs)) # noqa cls = import_string(utils.get_source(cls)) # If errors Blow up whole lot instead of processing few then blowing up cls().make_dicts(kwargs) # If doesn't blow up add to valid list valid_kwargs.append((cls, kwargs)) # If whole set has been validated: for cls, kwargs in valid_kwargs: new_object = cls().save_transaction(kwargs, live=live) obj_list.append(new_object) # Returns list of generated objects/messages (depending if live or not). return obj_list
def setUp(self): self.user = User.objects.create_user('test_staff_user', '*****@*****.**', '1234') self.user.is_staff = True self.user.save() # create accounts self.a1 = Account.objects.create(element='01', number='0150', name='a1') self.a2 = Account.objects.create(element='01', number='0100', name='a2') # create creditors objects entity = Entity.objects.create(code='a', name='a') self.creditor = Creditor.objects.create(entity=entity) # create bank_reconciliation objects self.ba = BankAccount(account=self.a1, bank='CBA') self.ba.save() # banktransacion created, transaction matched # Payment 1 t1_value = Decimal(350) lines = (self.a1, self.a2, t1_value) self.t1 = Transaction(date=date(2017, 6, 16), value=0, user=self.user, source="{}".format(BankAccount.__module__)) self.t1.save(lines=lines) self.b1 = BankLine(date=date(2017, 6, 16), value=t1_value, bank_account=self.ba, line_dump='Test Transaction 1', description='Test Transaction 1') self.b1.save() self.e1 = BankEntry.objects.create(transaction=self.t1, bank_line=self.b1) self.p1 = CreditorPayment(relation=self.creditor, bank_entry=self.e1, user=self.user) self.p1.save() # Payment 2 t2_value = Decimal(20) lines = (self.a1, self.a2, t2_value) self.t2 = Transaction(date=date(2017, 6, 16), value=0, user=self.user, source="{}".format(BankAccount.__module__)) self.t2.save(lines=lines) self.b2 = BankLine(date=date(2017, 6, 16), value=t2_value, bank_account=self.ba, line_dump='Test Transaction 2', description='Test Transaction 2') self.b2.save() self.e2 = BankEntry.objects.create(transaction=self.t2, bank_line=self.b2) self.p2 = CreditorPayment(relation=self.creditor, bank_entry=self.e2, user=self.user) self.p2.save() for x in range(1, 6): new_transaction = Transaction(user=self.user, date=date(2017, 5, 2) - timedelta(days=x * 30), source=get_source(CreditorInvoice)) new_transaction.save(lines=(self.a1, self.a2, 100.00)) new_invoice = CreditorInvoice(relation=self.creditor, transaction=new_transaction, invoice_number=x) new_invoice.save()
def test_get_cls_valid_model_source_failure(self): source = utils.get_source(Creditor) self.assertRaises(Exception, utils.get_cls, source)
def test_get_cls_valid_model_CreditorInvoice_source_passes(self): source = utils.get_source(CreditorInvoice) self.assertEqual(utils.get_cls(source), CreditorInvoice)
def test_get_cls_valid_model_JournalEntry_source_passes(self): source = utils.get_source(JournalEntry) self.assertEqual(utils.get_cls(source), JournalEntry)