django-ledger 0.8.2__py3-none-any.whl → 0.8.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-ledger might be problematic. Click here for more details.
- django_ledger/__init__.py +1 -1
- django_ledger/forms/data_import.py +63 -110
- django_ledger/io/roles.py +238 -303
- django_ledger/migrations/0027_alter_accountmodel_role_alter_receiptmodel_amount_and_more.py +159 -0
- django_ledger/models/chart_of_accounts.py +113 -180
- django_ledger/models/coa_default.py +1 -0
- django_ledger/models/customer.py +17 -26
- django_ledger/models/data_import.py +457 -153
- django_ledger/models/receipt.py +90 -121
- django_ledger/templates/django_ledger/data_import/data_import_job_txs.html +6 -0
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +66 -36
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +10 -10
- django_ledger/urls/data_import.py +3 -0
- django_ledger/views/data_import.py +37 -21
- {django_ledger-0.8.2.dist-info → django_ledger-0.8.2.2.dist-info}/METADATA +1 -1
- {django_ledger-0.8.2.dist-info → django_ledger-0.8.2.2.dist-info}/RECORD +20 -19
- {django_ledger-0.8.2.dist-info → django_ledger-0.8.2.2.dist-info}/WHEEL +0 -0
- {django_ledger-0.8.2.dist-info → django_ledger-0.8.2.2.dist-info}/licenses/AUTHORS.md +0 -0
- {django_ledger-0.8.2.dist-info → django_ledger-0.8.2.2.dist-info}/licenses/LICENSE +0 -0
- {django_ledger-0.8.2.dist-info → django_ledger-0.8.2.2.dist-info}/top_level.txt +0 -0
|
@@ -33,6 +33,7 @@ from django.db.models import (
|
|
|
33
33
|
)
|
|
34
34
|
from django.db.models.functions import Coalesce
|
|
35
35
|
from django.db.models.signals import pre_save
|
|
36
|
+
from django.urls import reverse
|
|
36
37
|
from django.utils.translation import gettext_lazy as _
|
|
37
38
|
|
|
38
39
|
from django_ledger.io import ASSET_CA_CASH, CREDIT, DEBIT
|
|
@@ -159,9 +160,7 @@ class ImportJobModelManager(Manager):
|
|
|
159
160
|
)
|
|
160
161
|
|
|
161
162
|
@deprecated_entity_slug_behavior
|
|
162
|
-
def for_entity(
|
|
163
|
-
self, entity_model: Union[EntityModel, str, UUID] = None, **kwargs
|
|
164
|
-
) -> ImportJobModelQuerySet:
|
|
163
|
+
def for_entity(self, entity_model: Union[EntityModel, str, UUID] = None, **kwargs) -> ImportJobModelQuerySet:
|
|
165
164
|
qs = self.get_queryset()
|
|
166
165
|
if 'user_model' in kwargs:
|
|
167
166
|
warnings.warn(
|
|
@@ -211,12 +210,6 @@ class ImportJobModelAbstract(CreateUpdateMixIn):
|
|
|
211
210
|
Indicates whether the import job has been completed.
|
|
212
211
|
objects : ImportJobModelManager
|
|
213
212
|
The default manager for the model.
|
|
214
|
-
|
|
215
|
-
Meta
|
|
216
|
-
----
|
|
217
|
-
This class is abstract and serves as a base for other models.
|
|
218
|
-
It includes additional metadata such as field verbose names
|
|
219
|
-
and database indexing.
|
|
220
213
|
"""
|
|
221
214
|
|
|
222
215
|
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
|
|
@@ -234,9 +227,7 @@ class ImportJobModelAbstract(CreateUpdateMixIn):
|
|
|
234
227
|
null=True,
|
|
235
228
|
blank=True,
|
|
236
229
|
)
|
|
237
|
-
completed = models.BooleanField(
|
|
238
|
-
default=False, verbose_name=_('Import Job Completed')
|
|
239
|
-
)
|
|
230
|
+
completed = models.BooleanField(default=False, verbose_name=_('Import Job Completed'))
|
|
240
231
|
objects = ImportJobModelManager()
|
|
241
232
|
|
|
242
233
|
class Meta:
|
|
@@ -304,9 +295,7 @@ class ImportJobModelAbstract(CreateUpdateMixIn):
|
|
|
304
295
|
True if both `ledger_model_id` and `bank_account_model_id` attributes
|
|
305
296
|
are set (not None), otherwise False.
|
|
306
297
|
"""
|
|
307
|
-
return all(
|
|
308
|
-
[self.ledger_model_id is not None, self.bank_account_model_id is not None]
|
|
309
|
-
)
|
|
298
|
+
return all([self.ledger_model_id is not None, self.bank_account_model_id is not None])
|
|
310
299
|
|
|
311
300
|
def configure(self, commit: bool = True):
|
|
312
301
|
"""
|
|
@@ -323,15 +312,31 @@ class ImportJobModelAbstract(CreateUpdateMixIn):
|
|
|
323
312
|
"""
|
|
324
313
|
if not self.is_configured():
|
|
325
314
|
if self.ledger_model_id is None:
|
|
326
|
-
self.ledger_model = self.bank_account_model.entity_model.create_ledger(
|
|
327
|
-
name=self.description
|
|
328
|
-
)
|
|
315
|
+
self.ledger_model = self.bank_account_model.entity_model.create_ledger(name=self.description)
|
|
329
316
|
if commit:
|
|
330
317
|
self.save(update_fields=['ledger_model'])
|
|
331
318
|
|
|
332
319
|
def get_delete_message(self) -> str:
|
|
333
320
|
return _(f'Are you sure you want to delete Import Job {self.description}?')
|
|
334
321
|
|
|
322
|
+
def get_data_import_url(self) -> str:
|
|
323
|
+
return reverse(
|
|
324
|
+
'django_ledger:data-import-job-txs',
|
|
325
|
+
kwargs={
|
|
326
|
+
'entity_slug': self.entity_slug,
|
|
327
|
+
'job_pk': self.uuid,
|
|
328
|
+
},
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def get_data_import_reset_url(self) -> str:
|
|
332
|
+
return reverse(
|
|
333
|
+
'django_ledger:data-import-job-txs-undo',
|
|
334
|
+
kwargs={
|
|
335
|
+
'entity_slug': self.entity_slug,
|
|
336
|
+
'job_pk': self.uuid,
|
|
337
|
+
},
|
|
338
|
+
)
|
|
339
|
+
|
|
335
340
|
|
|
336
341
|
class StagedTransactionModelValidationError(ValidationError):
|
|
337
342
|
"""
|
|
@@ -356,6 +361,71 @@ class StagedTransactionModelQuerySet(QuerySet):
|
|
|
356
361
|
relationships.
|
|
357
362
|
"""
|
|
358
363
|
|
|
364
|
+
def for_entity(self, entity_model: 'Union[EntityModel, UUID, str]') -> 'StagedTransactionModelQuerySet':
|
|
365
|
+
"""
|
|
366
|
+
Filters the queryset based on the type of the provided entity model.
|
|
367
|
+
|
|
368
|
+
The method accepts entity identifiers of varying formats including instances
|
|
369
|
+
of `EntityModel`, UUIDs, or string slugs and filters the query accordingly.
|
|
370
|
+
If an invalid type is provided, a validation error is raised.
|
|
371
|
+
|
|
372
|
+
Parameters
|
|
373
|
+
----------
|
|
374
|
+
entity_model : Union[EntityModel, UUID, str]
|
|
375
|
+
The entity identifier used to filter the queryset. Can be an `EntityModel` instance,
|
|
376
|
+
a UUID, or a string representing the slug of the entity.
|
|
377
|
+
|
|
378
|
+
Returns
|
|
379
|
+
-------
|
|
380
|
+
StagedTransactionModelQuerySet
|
|
381
|
+
A filtered queryset of staged transactions based on the provided entity model.
|
|
382
|
+
|
|
383
|
+
Raises
|
|
384
|
+
------
|
|
385
|
+
StagedTransactionModelValidationError
|
|
386
|
+
If the `entity_model` provided is not an instance of `EntityModel`, UUID, or string.
|
|
387
|
+
"""
|
|
388
|
+
if isinstance(entity_model, UUID):
|
|
389
|
+
return self.filter(import_job__ledger_model__entity_id=entity_model)
|
|
390
|
+
elif isinstance(entity_model, str):
|
|
391
|
+
return self.filter(import_job__ledger_model__entity__slug__exact=entity_model)
|
|
392
|
+
elif isinstance(entity_model, EntityModel):
|
|
393
|
+
return self.filter(import_job__ledger_model__entity=entity_model)
|
|
394
|
+
raise StagedTransactionModelValidationError(
|
|
395
|
+
message=f'Must pass an instance of EntityMode, UUID or str. Got {entity_model.__class__.__name__}'
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def for_import_job(self, import_job_model: 'Union[ImportJobModel | UUID]') -> 'StagedTransactionModelQuerySet':
|
|
399
|
+
"""
|
|
400
|
+
Filters the queryset based on the provided import job model or UUID.
|
|
401
|
+
|
|
402
|
+
This method evaluates whether the argument is an instance of ImportJobModel or
|
|
403
|
+
UUID and filters the queryset accordingly. If the argument is neither of these
|
|
404
|
+
types, it raises a validation error.
|
|
405
|
+
|
|
406
|
+
Parameters
|
|
407
|
+
----------
|
|
408
|
+
import_job_model : Union[ImportJobModel, UUID]
|
|
409
|
+
The import job model instance or UUID to filter the queryset by.
|
|
410
|
+
|
|
411
|
+
Returns
|
|
412
|
+
-------
|
|
413
|
+
StagedTransactionModelQuerySet
|
|
414
|
+
A queryset filtered by the given import job model or UUID.
|
|
415
|
+
|
|
416
|
+
Raises
|
|
417
|
+
------
|
|
418
|
+
StagedTransactionModelValidationError
|
|
419
|
+
If the provided argument is not an instance of ImportJobModel or UUID.
|
|
420
|
+
"""
|
|
421
|
+
if isinstance(import_job_model, ImportJobModel):
|
|
422
|
+
return self.filter(import_job=import_job_model)
|
|
423
|
+
elif isinstance(import_job_model, UUID):
|
|
424
|
+
return self.filter(import_job_id=import_job_model)
|
|
425
|
+
raise StagedTransactionModelValidationError(
|
|
426
|
+
message=f'Must pass an instance of ImportJobModel, UUID. Got {import_job_model.__class__.__name__}'
|
|
427
|
+
)
|
|
428
|
+
|
|
359
429
|
def is_pending(self):
|
|
360
430
|
"""
|
|
361
431
|
Determines if there are any pending transactions.
|
|
@@ -438,7 +508,7 @@ class StagedTransactionModelManager(Manager):
|
|
|
438
508
|
Fetch and annotate the queryset with related fields and calculated annotations.
|
|
439
509
|
"""
|
|
440
510
|
|
|
441
|
-
def get_queryset(self):
|
|
511
|
+
def get_queryset(self) -> StagedTransactionModelQuerySet:
|
|
442
512
|
"""
|
|
443
513
|
Fetch and annotate the queryset for staged transaction models to include additional
|
|
444
514
|
related fields and calculated annotations for further processing and sorting.
|
|
@@ -471,15 +541,13 @@ class StagedTransactionModelManager(Manager):
|
|
|
471
541
|
'parent',
|
|
472
542
|
'parent__account_model',
|
|
473
543
|
'parent__unit_model',
|
|
544
|
+
'receiptmodel',
|
|
474
545
|
)
|
|
475
546
|
.annotate(
|
|
476
|
-
|
|
547
|
+
_entity_slug=F('import_job__bank_account_model__entity_model__slug'),
|
|
477
548
|
entity_unit=F('transaction_model__journal_entry__entity_unit__name'),
|
|
478
|
-
_receipt_uuid=F('receiptmodel__uuid'),
|
|
479
549
|
children_count=Count('split_transaction_set'),
|
|
480
|
-
children_mapped_count=Count(
|
|
481
|
-
'split_transaction_set__account_model__uuid'
|
|
482
|
-
),
|
|
550
|
+
children_mapped_count=Count('split_transaction_set__account_model__uuid'),
|
|
483
551
|
total_amount_split=Coalesce(
|
|
484
552
|
Sum('split_transaction_set__amount_split'),
|
|
485
553
|
Value(value=0.00, output_field=DecimalField()),
|
|
@@ -488,10 +556,10 @@ class StagedTransactionModelManager(Manager):
|
|
|
488
556
|
When(parent_id__isnull=True, then=F('uuid')),
|
|
489
557
|
When(parent_id__isnull=False, then=F('parent_id')),
|
|
490
558
|
),
|
|
559
|
+
_receipt_uuid=F('receiptmodel__uuid'),
|
|
491
560
|
)
|
|
492
561
|
.annotate(
|
|
493
|
-
children_mapping_pending_count=F('children_count')
|
|
494
|
-
- F('children_mapped_count'),
|
|
562
|
+
children_mapping_pending_count=F('children_count') - F('children_mapped_count'),
|
|
495
563
|
)
|
|
496
564
|
.annotate(
|
|
497
565
|
children_mapping_done=Case(
|
|
@@ -504,6 +572,8 @@ class StagedTransactionModelManager(Manager):
|
|
|
504
572
|
When(
|
|
505
573
|
condition=(
|
|
506
574
|
Q(children_count__exact=0)
|
|
575
|
+
& Q(bundle_split=True)
|
|
576
|
+
& Q(parent__isnull=True)
|
|
507
577
|
& Q(account_model__isnull=False)
|
|
508
578
|
& Q(parent__isnull=True)
|
|
509
579
|
& Q(transaction_model__isnull=True)
|
|
@@ -515,19 +585,19 @@ class StagedTransactionModelManager(Manager):
|
|
|
515
585
|
& Q(customer_model__isnull=True)
|
|
516
586
|
)
|
|
517
587
|
| (
|
|
518
|
-
# transaction
|
|
588
|
+
# sales/expense transaction...
|
|
519
589
|
Q(receipt_type__isnull=False)
|
|
520
590
|
& (
|
|
521
|
-
(
|
|
522
|
-
|
|
523
|
-
& Q(customer_model__isnull=True)
|
|
524
|
-
)
|
|
525
|
-
| (
|
|
526
|
-
Q(vendor_model__isnull=True)
|
|
527
|
-
& Q(customer_model__isnull=False)
|
|
528
|
-
)
|
|
591
|
+
(Q(vendor_model__isnull=False) & Q(customer_model__isnull=True))
|
|
592
|
+
| (Q(vendor_model__isnull=True) & Q(customer_model__isnull=False))
|
|
529
593
|
)
|
|
530
594
|
)
|
|
595
|
+
| (
|
|
596
|
+
# sales/expense transaction...
|
|
597
|
+
Q(receipt_type__exact=ReceiptModel.TRANSFER_RECEIPT)
|
|
598
|
+
& Q(vendor_model__isnull=True)
|
|
599
|
+
& Q(customer_model__isnull=True)
|
|
600
|
+
)
|
|
531
601
|
)
|
|
532
602
|
),
|
|
533
603
|
then=True,
|
|
@@ -539,31 +609,44 @@ class StagedTransactionModelManager(Manager):
|
|
|
539
609
|
# will import the transaction as is...
|
|
540
610
|
(
|
|
541
611
|
Q(children_count__gt=0)
|
|
612
|
+
& Q(bundle_split=True)
|
|
542
613
|
& Q(receipt_type__isnull=True)
|
|
543
614
|
& Q(children_count=F('children_mapped_count'))
|
|
544
615
|
& Q(total_amount_split__exact=F('amount'))
|
|
545
616
|
& Q(parent__isnull=True)
|
|
546
617
|
& Q(transaction_model__isnull=True)
|
|
618
|
+
& Q(customer_model__isnull=True)
|
|
619
|
+
& Q(vendor_model__isnull=False)
|
|
547
620
|
)
|
|
548
|
-
#
|
|
621
|
+
# BUNDLED...
|
|
622
|
+
# a receipt type is assigned... at least a customer or vendor is selected...
|
|
549
623
|
| (
|
|
550
624
|
Q(children_count__gt=0)
|
|
625
|
+
& Q(parent__isnull=True)
|
|
626
|
+
& Q(bundle_split=True)
|
|
551
627
|
& Q(receipt_type__isnull=False)
|
|
552
628
|
& (
|
|
553
|
-
(
|
|
554
|
-
|
|
555
|
-
& Q(customer_model__isnull=True)
|
|
556
|
-
)
|
|
557
|
-
| (
|
|
558
|
-
Q(vendor_model__isnull=True)
|
|
559
|
-
& Q(customer_model__isnull=False)
|
|
560
|
-
)
|
|
629
|
+
(Q(vendor_model__isnull=False) & Q(customer_model__isnull=True))
|
|
630
|
+
| (Q(vendor_model__isnull=True) & Q(customer_model__isnull=False))
|
|
561
631
|
)
|
|
562
632
|
& Q(children_count=F('children_mapped_count'))
|
|
563
633
|
& Q(total_amount_split__exact=F('amount'))
|
|
564
634
|
& Q(parent__isnull=True)
|
|
565
635
|
& Q(transaction_model__isnull=True)
|
|
566
636
|
)
|
|
637
|
+
# NOT BUNDLED...
|
|
638
|
+
# a receipt type is assigned... at least a customer or vendor is selected...
|
|
639
|
+
| (
|
|
640
|
+
Q(children_count__gt=0)
|
|
641
|
+
& Q(parent__isnull=True)
|
|
642
|
+
& Q(bundle_split=False)
|
|
643
|
+
& Q(receipt_type__isnull=True)
|
|
644
|
+
& Q(vendor_model__isnull=True)
|
|
645
|
+
& Q(customer_model__isnull=True)
|
|
646
|
+
& Q(children_count=F('children_mapped_count'))
|
|
647
|
+
& Q(total_amount_split__exact=F('amount'))
|
|
648
|
+
& Q(transaction_model__isnull=True)
|
|
649
|
+
)
|
|
567
650
|
),
|
|
568
651
|
then=True,
|
|
569
652
|
),
|
|
@@ -664,14 +747,10 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
664
747
|
related_name='split_transaction_set',
|
|
665
748
|
verbose_name=_('Parent Transaction'),
|
|
666
749
|
)
|
|
667
|
-
import_job = models.ForeignKey(
|
|
668
|
-
'django_ledger.ImportJobModel', on_delete=models.CASCADE
|
|
669
|
-
)
|
|
750
|
+
import_job = models.ForeignKey('django_ledger.ImportJobModel', on_delete=models.CASCADE)
|
|
670
751
|
fit_id = models.CharField(max_length=100)
|
|
671
752
|
date_posted = models.DateField(verbose_name=_('Date Posted'))
|
|
672
|
-
bundle_split = models.BooleanField(
|
|
673
|
-
default=True, verbose_name=_('Bundle Split Transactions')
|
|
674
|
-
)
|
|
753
|
+
bundle_split = models.BooleanField(default=True, verbose_name=_('Bundle Split Transactions'))
|
|
675
754
|
activity = models.CharField(
|
|
676
755
|
choices=JournalEntryModel.ACTIVITIES,
|
|
677
756
|
max_length=20,
|
|
@@ -679,18 +758,12 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
679
758
|
blank=True,
|
|
680
759
|
verbose_name=_('Proposed Activity'),
|
|
681
760
|
)
|
|
682
|
-
amount = models.DecimalField(
|
|
683
|
-
|
|
684
|
-
)
|
|
685
|
-
amount_split = models.DecimalField(
|
|
686
|
-
decimal_places=2, max_digits=15, null=True, blank=True
|
|
687
|
-
)
|
|
761
|
+
amount = models.DecimalField(decimal_places=2, max_digits=15, editable=False, null=True, blank=True)
|
|
762
|
+
amount_split = models.DecimalField(decimal_places=2, max_digits=15, null=True, blank=True)
|
|
688
763
|
name = models.CharField(max_length=200, blank=True, null=True)
|
|
689
764
|
memo = models.CharField(max_length=200, blank=True, null=True)
|
|
690
765
|
|
|
691
|
-
account_model = models.ForeignKey(
|
|
692
|
-
'django_ledger.AccountModel', on_delete=models.RESTRICT, null=True, blank=True
|
|
693
|
-
)
|
|
766
|
+
account_model = models.ForeignKey('django_ledger.AccountModel', on_delete=models.RESTRICT, null=True, blank=True)
|
|
694
767
|
|
|
695
768
|
unit_model = models.ForeignKey(
|
|
696
769
|
'django_ledger.EntityUnitModel',
|
|
@@ -731,7 +804,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
731
804
|
help_text=_('The Customer associated with the transaction.'),
|
|
732
805
|
)
|
|
733
806
|
|
|
734
|
-
objects = StagedTransactionModelManager()
|
|
807
|
+
objects = StagedTransactionModelManager.from_queryset(queryset_class=StagedTransactionModelQuerySet)()
|
|
735
808
|
|
|
736
809
|
class Meta:
|
|
737
810
|
abstract = True
|
|
@@ -823,9 +896,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
823
896
|
'amount': abs(child_txs_model.amount_split),
|
|
824
897
|
'amount_staged': child_txs_model.amount_split,
|
|
825
898
|
'unit_model': child_txs_model.unit_model,
|
|
826
|
-
'tx_type': CREDIT
|
|
827
|
-
if not child_txs_model.amount_split < 0.00
|
|
828
|
-
else DEBIT,
|
|
899
|
+
'tx_type': CREDIT if not child_txs_model.amount_split < 0.00 else DEBIT,
|
|
829
900
|
'description': child_txs_model.name,
|
|
830
901
|
'staged_tx_model': child_txs_model,
|
|
831
902
|
}
|
|
@@ -892,7 +963,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
892
963
|
return self.amount
|
|
893
964
|
|
|
894
965
|
def is_sales(self) -> bool:
|
|
895
|
-
if self.is_children():
|
|
966
|
+
if self.is_children() and self.is_bundled():
|
|
896
967
|
return self.parent.is_sales()
|
|
897
968
|
return any(
|
|
898
969
|
[
|
|
@@ -902,7 +973,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
902
973
|
)
|
|
903
974
|
|
|
904
975
|
def is_expense(self) -> bool:
|
|
905
|
-
if self.is_children():
|
|
976
|
+
if self.is_children() and self.is_bundled():
|
|
906
977
|
return self.parent.is_expense()
|
|
907
978
|
return any(
|
|
908
979
|
[
|
|
@@ -911,6 +982,14 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
911
982
|
]
|
|
912
983
|
)
|
|
913
984
|
|
|
985
|
+
def is_transfer(self) -> bool:
|
|
986
|
+
return self.receipt_type == ReceiptModel.TRANSFER_RECEIPT
|
|
987
|
+
|
|
988
|
+
def is_debt_payment(self) -> bool:
|
|
989
|
+
if self.is_children() and self.is_bundled():
|
|
990
|
+
return self.parent.is_debt_payment()
|
|
991
|
+
return self.receipt_type == ReceiptModel.DEBT_PAYMENT
|
|
992
|
+
|
|
914
993
|
def is_imported(self) -> bool:
|
|
915
994
|
"""
|
|
916
995
|
Determines if the necessary models have been imported for the system to function
|
|
@@ -962,21 +1041,8 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
962
1041
|
"""
|
|
963
1042
|
return self.account_model_id is not None
|
|
964
1043
|
|
|
965
|
-
def
|
|
966
|
-
|
|
967
|
-
Checks whether the current instance represents a single entry.
|
|
968
|
-
|
|
969
|
-
This method determines if the current object qualifies as a single entry
|
|
970
|
-
by ensuring that it both does not have children and is not considered a
|
|
971
|
-
child of any other entry. The result is a boolean value indicating
|
|
972
|
-
whether the entry meets these criteria.
|
|
973
|
-
|
|
974
|
-
Returns
|
|
975
|
-
-------
|
|
976
|
-
bool
|
|
977
|
-
True if the entry is a single, standalone entry; False otherwise.
|
|
978
|
-
"""
|
|
979
|
-
return all([not self.is_children(), not self.has_children()])
|
|
1044
|
+
def is_parent(self) -> bool:
|
|
1045
|
+
return self.parent_id is None
|
|
980
1046
|
|
|
981
1047
|
def is_children(self) -> bool:
|
|
982
1048
|
"""
|
|
@@ -991,11 +1057,12 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
991
1057
|
True if the object has a valid `parent_id`, indicating it is a child entity;
|
|
992
1058
|
False otherwise.
|
|
993
1059
|
"""
|
|
994
|
-
return
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1060
|
+
return self.parent_id is not None
|
|
1061
|
+
|
|
1062
|
+
def is_bundled(self) -> bool:
|
|
1063
|
+
if not self.parent_id:
|
|
1064
|
+
return self.bundle_split is True
|
|
1065
|
+
return self.parent.is_bundled()
|
|
999
1066
|
|
|
1000
1067
|
def has_activity(self) -> bool:
|
|
1001
1068
|
"""
|
|
@@ -1032,25 +1099,198 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1032
1099
|
return False
|
|
1033
1100
|
return getattr(self, 'children_count') > 0
|
|
1034
1101
|
|
|
1102
|
+
# TX Cases...
|
|
1103
|
+
|
|
1104
|
+
def is_single(self) -> bool:
|
|
1105
|
+
"""
|
|
1106
|
+
Determine if the current object is an original import.
|
|
1107
|
+
|
|
1108
|
+
This method checks whether the current object is neither a child nor
|
|
1109
|
+
has any children associated with it. If both checks return False,
|
|
1110
|
+
the object is considered original.
|
|
1111
|
+
|
|
1112
|
+
Returns
|
|
1113
|
+
-------
|
|
1114
|
+
bool
|
|
1115
|
+
True if the object is original, otherwise False.
|
|
1116
|
+
"""
|
|
1117
|
+
return all([not self.is_children(), not self.has_children()])
|
|
1118
|
+
|
|
1119
|
+
def is_single_no_receipt(self) -> bool:
|
|
1120
|
+
return all(
|
|
1121
|
+
[
|
|
1122
|
+
self.is_single(),
|
|
1123
|
+
not self.has_receipt(),
|
|
1124
|
+
]
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1127
|
+
def is_single_has_receipt(self) -> bool:
|
|
1128
|
+
return all(
|
|
1129
|
+
[
|
|
1130
|
+
self.is_single(),
|
|
1131
|
+
self.has_receipt(),
|
|
1132
|
+
]
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
def is_parent_is_bundled_no_receipt(self) -> bool:
|
|
1136
|
+
return all(
|
|
1137
|
+
[
|
|
1138
|
+
self.is_parent(),
|
|
1139
|
+
self.has_children(),
|
|
1140
|
+
self.is_bundled(),
|
|
1141
|
+
not self.has_receipt(),
|
|
1142
|
+
]
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
def is_parent_is_bundled_has_receipt(self) -> bool:
|
|
1146
|
+
return all(
|
|
1147
|
+
[
|
|
1148
|
+
self.is_parent(),
|
|
1149
|
+
self.has_children(),
|
|
1150
|
+
self.is_bundled(),
|
|
1151
|
+
self.has_receipt(),
|
|
1152
|
+
]
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
def is_parent_not_bundled_has_receipt(self) -> bool:
|
|
1156
|
+
return all(
|
|
1157
|
+
[
|
|
1158
|
+
self.is_parent(),
|
|
1159
|
+
self.has_children(),
|
|
1160
|
+
not self.is_bundled(),
|
|
1161
|
+
self.has_receipt(),
|
|
1162
|
+
]
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
def is_parent_not_bundled_no_receipt(self) -> bool:
|
|
1166
|
+
return all(
|
|
1167
|
+
[
|
|
1168
|
+
self.is_parent(),
|
|
1169
|
+
self.has_children(),
|
|
1170
|
+
not self.is_bundled(),
|
|
1171
|
+
not self.has_receipt(),
|
|
1172
|
+
]
|
|
1173
|
+
)
|
|
1174
|
+
|
|
1175
|
+
def is_child_is_bundled_no_receipt(self) -> bool:
|
|
1176
|
+
return all(
|
|
1177
|
+
[
|
|
1178
|
+
self.is_children(),
|
|
1179
|
+
self.is_bundled(),
|
|
1180
|
+
not self.has_receipt(),
|
|
1181
|
+
]
|
|
1182
|
+
)
|
|
1183
|
+
|
|
1184
|
+
def is_child_is_bundled_has_receipt(self) -> bool:
|
|
1185
|
+
return all(
|
|
1186
|
+
[
|
|
1187
|
+
self.is_children(),
|
|
1188
|
+
self.is_bundled(),
|
|
1189
|
+
self.has_receipt(),
|
|
1190
|
+
]
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
def is_child_not_bundled_has_receipt(self) -> bool:
|
|
1194
|
+
return all(
|
|
1195
|
+
[
|
|
1196
|
+
self.is_children(),
|
|
1197
|
+
not self.is_bundled(),
|
|
1198
|
+
self.has_receipt(),
|
|
1199
|
+
]
|
|
1200
|
+
)
|
|
1201
|
+
|
|
1202
|
+
def is_child_not_bundled_no_receipt(self) -> bool:
|
|
1203
|
+
return all(
|
|
1204
|
+
[
|
|
1205
|
+
self.is_children(),
|
|
1206
|
+
not self.is_bundled(),
|
|
1207
|
+
not self.has_receipt(),
|
|
1208
|
+
]
|
|
1209
|
+
)
|
|
1210
|
+
|
|
1211
|
+
@property
|
|
1212
|
+
def entity_slug(self) -> str:
|
|
1213
|
+
return getattr(self, '_entity_slug')
|
|
1214
|
+
|
|
1035
1215
|
@property
|
|
1036
1216
|
def receipt_uuid(self):
|
|
1037
1217
|
try:
|
|
1038
1218
|
return getattr(self, '_receipt_uuid')
|
|
1039
1219
|
except AttributeError:
|
|
1040
1220
|
pass
|
|
1041
|
-
return
|
|
1221
|
+
return None
|
|
1042
1222
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1223
|
+
# Data Import Field Visibility...
|
|
1224
|
+
|
|
1225
|
+
def can_have_amount_split(self):
|
|
1226
|
+
if self.is_transfer():
|
|
1227
|
+
return False
|
|
1228
|
+
return self.is_children()
|
|
1229
|
+
|
|
1230
|
+
def can_have_bundle_split(self):
|
|
1231
|
+
if self.is_transfer():
|
|
1232
|
+
return False
|
|
1233
|
+
return all([self.is_parent()])
|
|
1234
|
+
|
|
1235
|
+
def can_have_receipt(self) -> bool:
|
|
1236
|
+
if any(
|
|
1237
|
+
[
|
|
1238
|
+
self.is_single_no_receipt(),
|
|
1239
|
+
self.is_single_has_receipt(),
|
|
1240
|
+
self.is_parent_is_bundled_no_receipt(),
|
|
1241
|
+
self.is_parent_is_bundled_has_receipt(),
|
|
1242
|
+
self.is_child_not_bundled_no_receipt(),
|
|
1243
|
+
self.is_child_not_bundled_has_receipt(),
|
|
1244
|
+
]
|
|
1245
|
+
):
|
|
1246
|
+
return True
|
|
1247
|
+
return False
|
|
1248
|
+
|
|
1249
|
+
def can_have_vendor(self) -> bool:
|
|
1250
|
+
if self.is_transfer():
|
|
1251
|
+
return False
|
|
1252
|
+
if all(
|
|
1253
|
+
[
|
|
1254
|
+
any(
|
|
1255
|
+
[
|
|
1256
|
+
self.is_expense(),
|
|
1257
|
+
self.is_debt_payment(),
|
|
1258
|
+
]
|
|
1259
|
+
),
|
|
1260
|
+
any(
|
|
1261
|
+
[
|
|
1262
|
+
self.is_single_has_receipt(),
|
|
1263
|
+
self.is_parent_is_bundled_has_receipt(),
|
|
1264
|
+
self.is_child_not_bundled_has_receipt(),
|
|
1265
|
+
]
|
|
1266
|
+
),
|
|
1267
|
+
]
|
|
1268
|
+
):
|
|
1269
|
+
return True
|
|
1270
|
+
return False
|
|
1271
|
+
|
|
1272
|
+
def can_have_customer(self) -> bool:
|
|
1273
|
+
if self.is_transfer():
|
|
1274
|
+
return False
|
|
1275
|
+
if all(
|
|
1276
|
+
[
|
|
1277
|
+
self.is_sales(),
|
|
1278
|
+
any(
|
|
1279
|
+
[
|
|
1280
|
+
self.is_single_has_receipt(),
|
|
1281
|
+
self.is_parent_is_bundled_has_receipt(),
|
|
1282
|
+
self.is_child_not_bundled_has_receipt(),
|
|
1283
|
+
]
|
|
1284
|
+
),
|
|
1285
|
+
]
|
|
1286
|
+
):
|
|
1287
|
+
return True
|
|
1288
|
+
return False
|
|
1047
1289
|
|
|
1048
1290
|
def has_receipt(self) -> bool:
|
|
1049
|
-
if self.is_children():
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
)
|
|
1053
|
-
return all([self.receipt_type is not None, self.receipt_uuid is not None])
|
|
1291
|
+
# if self.is_children() and self.is_bundled():
|
|
1292
|
+
# return self.parent.receipt_type is not None
|
|
1293
|
+
return self.receipt_type is not None
|
|
1054
1294
|
|
|
1055
1295
|
def has_mapped_receipt(self) -> bool:
|
|
1056
1296
|
if all(
|
|
@@ -1077,6 +1317,13 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1077
1317
|
return True
|
|
1078
1318
|
return False
|
|
1079
1319
|
|
|
1320
|
+
def can_unbundle(self) -> bool:
|
|
1321
|
+
if any([
|
|
1322
|
+
not self.is_single()
|
|
1323
|
+
]):
|
|
1324
|
+
return True
|
|
1325
|
+
return False
|
|
1326
|
+
|
|
1080
1327
|
def can_split(self) -> bool:
|
|
1081
1328
|
"""
|
|
1082
1329
|
Determines if the current object can be split based on its child status.
|
|
@@ -1090,7 +1337,15 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1090
1337
|
`True` if the object has no children and can be split, otherwise
|
|
1091
1338
|
`False`.
|
|
1092
1339
|
"""
|
|
1093
|
-
|
|
1340
|
+
if any(
|
|
1341
|
+
[
|
|
1342
|
+
self.is_single(),
|
|
1343
|
+
self.is_parent_is_bundled_has_receipt(),
|
|
1344
|
+
self.is_parent_is_bundled_no_receipt()
|
|
1345
|
+
]
|
|
1346
|
+
):
|
|
1347
|
+
return True
|
|
1348
|
+
return False
|
|
1094
1349
|
|
|
1095
1350
|
def can_have_unit(self) -> bool:
|
|
1096
1351
|
"""
|
|
@@ -1151,7 +1406,24 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1151
1406
|
return not self.has_children()
|
|
1152
1407
|
|
|
1153
1408
|
def can_have_activity(self) -> bool:
|
|
1154
|
-
|
|
1409
|
+
if self.is_transfer():
|
|
1410
|
+
return False
|
|
1411
|
+
if all(
|
|
1412
|
+
[
|
|
1413
|
+
self.is_mapped(),
|
|
1414
|
+
any(
|
|
1415
|
+
[
|
|
1416
|
+
self.is_single(),
|
|
1417
|
+
self.is_parent_is_bundled_has_receipt(),
|
|
1418
|
+
self.is_parent_is_bundled_no_receipt(),
|
|
1419
|
+
self.is_child_not_bundled_has_receipt(),
|
|
1420
|
+
self.is_child_not_bundled_no_receipt(),
|
|
1421
|
+
]
|
|
1422
|
+
),
|
|
1423
|
+
]
|
|
1424
|
+
):
|
|
1425
|
+
return True
|
|
1426
|
+
return False
|
|
1155
1427
|
|
|
1156
1428
|
def can_migrate(self, as_split: bool = False) -> bool:
|
|
1157
1429
|
"""
|
|
@@ -1179,13 +1451,33 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1179
1451
|
otherwise False.
|
|
1180
1452
|
"""
|
|
1181
1453
|
ready_to_import = getattr(self, 'ready_to_import')
|
|
1454
|
+
|
|
1182
1455
|
if not ready_to_import:
|
|
1183
1456
|
return False
|
|
1184
1457
|
|
|
1458
|
+
if ready_to_import:
|
|
1459
|
+
is_role_valid = self.is_role_mapping_valid(raise_exception=False)
|
|
1460
|
+
if is_role_valid:
|
|
1461
|
+
return True
|
|
1462
|
+
|
|
1185
1463
|
can_split_into_je = getattr(self, 'can_split_into_je')
|
|
1186
1464
|
if can_split_into_je and as_split:
|
|
1187
1465
|
return True
|
|
1188
|
-
|
|
1466
|
+
|
|
1467
|
+
return False
|
|
1468
|
+
|
|
1469
|
+
def can_migrate_receipt(self) -> bool:
|
|
1470
|
+
if self.has_receipt():
|
|
1471
|
+
ready_to_import = getattr(self, 'ready_to_import')
|
|
1472
|
+
if ready_to_import:
|
|
1473
|
+
if self.is_transfer():
|
|
1474
|
+
return True
|
|
1475
|
+
if any([
|
|
1476
|
+
self.is_single_has_receipt(),
|
|
1477
|
+
self.is_parent_is_bundled_has_receipt()
|
|
1478
|
+
]):
|
|
1479
|
+
return True
|
|
1480
|
+
return False
|
|
1189
1481
|
|
|
1190
1482
|
def can_import(self) -> bool:
|
|
1191
1483
|
return self.can_migrate()
|
|
@@ -1218,9 +1510,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1218
1510
|
"""
|
|
1219
1511
|
if not self.can_split():
|
|
1220
1512
|
if raise_exception:
|
|
1221
|
-
raise ImportJobModelValidationError(
|
|
1222
|
-
message=_(f'Staged Transaction {self.uuid} already split.')
|
|
1223
|
-
)
|
|
1513
|
+
raise ImportJobModelValidationError(message=_(f'Staged Transaction {self.uuid} already split.'))
|
|
1224
1514
|
return
|
|
1225
1515
|
|
|
1226
1516
|
if not self.has_children():
|
|
@@ -1294,20 +1584,16 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1294
1584
|
"""
|
|
1295
1585
|
if self.is_single() and self.is_mapped():
|
|
1296
1586
|
return {self.account_model.role}
|
|
1297
|
-
if self.
|
|
1587
|
+
if self.is_children() and not self.is_bundled() and self.is_mapped():
|
|
1588
|
+
return {self.account_model.role}
|
|
1589
|
+
if self.has_children() and self.is_bundled():
|
|
1298
1590
|
split_txs_qs = self.split_transaction_set.all()
|
|
1299
1591
|
if all([txs.is_mapped() for txs in split_txs_qs]):
|
|
1300
|
-
return set(
|
|
1301
|
-
[
|
|
1302
|
-
txs.account_model.role
|
|
1303
|
-
for txs in split_txs_qs
|
|
1304
|
-
if txs.account_model.role != ASSET_CA_CASH
|
|
1305
|
-
]
|
|
1306
|
-
)
|
|
1592
|
+
return set([txs.account_model.role for txs in split_txs_qs if txs.account_model.role != ASSET_CA_CASH])
|
|
1307
1593
|
return set()
|
|
1308
1594
|
|
|
1309
1595
|
def get_prospect_je_activity_try(
|
|
1310
|
-
self, raise_exception: bool = True, force_update: bool = False
|
|
1596
|
+
self, raise_exception: bool = True, force_update: bool = False, commit: bool = True
|
|
1311
1597
|
) -> Optional[str]:
|
|
1312
1598
|
"""
|
|
1313
1599
|
Retrieve or attempt to fetch the journal entry activity for the current prospect object.
|
|
@@ -1333,15 +1619,17 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1333
1619
|
The journal entry activity if successfully retrieved or updated; otherwise,
|
|
1334
1620
|
returns the existing activity or None if no activity is present.
|
|
1335
1621
|
"""
|
|
1336
|
-
|
|
1337
|
-
|
|
1622
|
+
if (
|
|
1623
|
+
force_update
|
|
1624
|
+
or all([self.is_children() and self.parent.has_activity(), not self.is_bundled(), not self.has_activity()])
|
|
1625
|
+
or all([not self.has_activity(), getattr(self, 'ready_to_import')])
|
|
1626
|
+
):
|
|
1338
1627
|
role_set = self.get_import_role_set()
|
|
1339
1628
|
if role_set is not None:
|
|
1340
1629
|
try:
|
|
1341
|
-
self.activity = JournalEntryModel.get_activity_from_roles(
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
self.save(update_fields=['activity'])
|
|
1630
|
+
self.activity = JournalEntryModel.get_activity_from_roles(role_set=role_set)
|
|
1631
|
+
if commit:
|
|
1632
|
+
self.save(update_fields=['activity'])
|
|
1345
1633
|
return self.activity
|
|
1346
1634
|
except ValidationError as e:
|
|
1347
1635
|
if raise_exception:
|
|
@@ -1362,6 +1650,14 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1362
1650
|
Optional[str]
|
|
1363
1651
|
The activity of the prospect journal entry if available, otherwise `None`.
|
|
1364
1652
|
"""
|
|
1653
|
+
if all(
|
|
1654
|
+
[
|
|
1655
|
+
self.is_parent(),
|
|
1656
|
+
not self.is_bundled(),
|
|
1657
|
+
self.has_children(),
|
|
1658
|
+
]
|
|
1659
|
+
):
|
|
1660
|
+
return None
|
|
1365
1661
|
return self.get_prospect_je_activity_try(raise_exception=False)
|
|
1366
1662
|
|
|
1367
1663
|
def get_prospect_je_activity_display(self) -> Optional[str]:
|
|
@@ -1405,9 +1701,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1405
1701
|
"""
|
|
1406
1702
|
if not self.has_activity():
|
|
1407
1703
|
try:
|
|
1408
|
-
activity = self.get_prospect_je_activity_try(
|
|
1409
|
-
raise_exception=raise_exception
|
|
1410
|
-
)
|
|
1704
|
+
activity = self.get_prospect_je_activity_try(raise_exception=raise_exception)
|
|
1411
1705
|
if activity is None:
|
|
1412
1706
|
return False
|
|
1413
1707
|
self.activity = activity
|
|
@@ -1447,9 +1741,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1447
1741
|
'Migrate transactions can only be performed on non-receipt transactions. Use migrate_receipt() instead.'
|
|
1448
1742
|
)
|
|
1449
1743
|
if not self.can_migrate():
|
|
1450
|
-
raise StagedTransactionModelValidationError(
|
|
1451
|
-
f'Transaction {self.uuid} is not ready to be migrated'
|
|
1452
|
-
)
|
|
1744
|
+
raise StagedTransactionModelValidationError(f'Transaction {self.uuid} is not ready to be migrated')
|
|
1453
1745
|
|
|
1454
1746
|
commit_dict = self.commit_dict(split_txs=split_txs)
|
|
1455
1747
|
import_job = self.import_job
|
|
@@ -1459,11 +1751,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1459
1751
|
with transaction.atomic():
|
|
1460
1752
|
staged_to_save = list()
|
|
1461
1753
|
for je_data in commit_dict:
|
|
1462
|
-
unit_model =
|
|
1463
|
-
self.unit_model
|
|
1464
|
-
if not split_txs
|
|
1465
|
-
else commit_dict[0][1]['unit_model']
|
|
1466
|
-
)
|
|
1754
|
+
unit_model = self.unit_model if not split_txs else commit_dict[0][1]['unit_model']
|
|
1467
1755
|
_, _ = ledger_model.commit_txs(
|
|
1468
1756
|
je_timestamp=self.date_posted,
|
|
1469
1757
|
je_unit_model=unit_model,
|
|
@@ -1486,19 +1774,13 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1486
1774
|
'Migrate receipts can only be performed on receipt transactions. Use migrate_transactions() instead.'
|
|
1487
1775
|
)
|
|
1488
1776
|
if not self.can_migrate():
|
|
1489
|
-
raise StagedTransactionModelValidationError(
|
|
1490
|
-
f'Transaction {self.uuid} is not ready to be migratedd'
|
|
1491
|
-
)
|
|
1777
|
+
raise StagedTransactionModelValidationError(f'Transaction {self.uuid} is not ready to be migratedd')
|
|
1492
1778
|
|
|
1493
1779
|
with transaction.atomic():
|
|
1494
|
-
receipt_model: ReceiptModel = self.generate_receipt_model(
|
|
1495
|
-
receipt_date=receipt_date, commit=True
|
|
1496
|
-
)
|
|
1780
|
+
receipt_model: ReceiptModel = self.generate_receipt_model(receipt_date=receipt_date, commit=True)
|
|
1497
1781
|
receipt_model.migrate_receipt()
|
|
1498
1782
|
|
|
1499
|
-
def generate_receipt_model(
|
|
1500
|
-
self, receipt_date: Optional[date] = None, commit: bool = False
|
|
1501
|
-
) -> ReceiptModel:
|
|
1783
|
+
def generate_receipt_model(self, receipt_date: Optional[date] = None, commit: bool = False) -> ReceiptModel:
|
|
1502
1784
|
if receipt_date:
|
|
1503
1785
|
if isinstance(receipt_date, datetime):
|
|
1504
1786
|
receipt_date = receipt_date.date()
|
|
@@ -1512,16 +1794,22 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1512
1794
|
amount=abs(self.amount),
|
|
1513
1795
|
unit_model=self.unit_model,
|
|
1514
1796
|
receipt_type=self.receipt_type,
|
|
1515
|
-
vendor_model=self.vendor_model if self.is_expense() else None,
|
|
1797
|
+
vendor_model=self.vendor_model if self.is_expense() or self.is_debt_payment() else None,
|
|
1516
1798
|
customer_model=self.customer_model if self.is_sales() else None,
|
|
1517
1799
|
charge_account=self.get_coa_account_model(),
|
|
1518
|
-
receipt_account=self.account_model,
|
|
1800
|
+
receipt_account=self.account_model if self.is_mapped() else None,
|
|
1519
1801
|
staged_transaction_model=self,
|
|
1520
1802
|
commit=True,
|
|
1521
1803
|
)
|
|
1522
1804
|
|
|
1523
1805
|
return receipt_model
|
|
1524
1806
|
|
|
1807
|
+
def can_undo_import(self):
|
|
1808
|
+
if all([self.is_children(), self.is_bundled()]):
|
|
1809
|
+
return False
|
|
1810
|
+
|
|
1811
|
+
return True
|
|
1812
|
+
|
|
1525
1813
|
# UNDO
|
|
1526
1814
|
def undo_import(self, raise_exception: bool = True):
|
|
1527
1815
|
"""
|
|
@@ -1536,6 +1824,12 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1536
1824
|
If there is no receipt model or transaction model to undo.
|
|
1537
1825
|
|
|
1538
1826
|
"""
|
|
1827
|
+
if not self.can_undo_import():
|
|
1828
|
+
if raise_exception:
|
|
1829
|
+
raise StagedTransactionModelValidationError(
|
|
1830
|
+
message='Cannot undo children bundled import. Must undo the parent import.'
|
|
1831
|
+
)
|
|
1832
|
+
|
|
1539
1833
|
with transaction.atomic():
|
|
1540
1834
|
# Receipt import case...
|
|
1541
1835
|
try:
|
|
@@ -1555,15 +1849,18 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1555
1849
|
if self.transaction_model_id:
|
|
1556
1850
|
tx_model = self.transaction_model
|
|
1557
1851
|
journal_entry_model = tx_model.journal_entry
|
|
1558
|
-
|
|
1852
|
+
|
|
1853
|
+
if journal_entry_model.can_unlock() and journal_entry_model.can_unpost():
|
|
1854
|
+
journal_entry_model.unlock(raise_exception=False)
|
|
1855
|
+
journal_entry_model.unpost(raise_exception=False)
|
|
1856
|
+
journal_entry_model.delete()
|
|
1857
|
+
|
|
1559
1858
|
self.transaction_model = None
|
|
1560
1859
|
self.save(update_fields=['transaction_model', 'updated'])
|
|
1561
1860
|
return
|
|
1562
1861
|
|
|
1563
1862
|
if raise_exception:
|
|
1564
|
-
raise StagedTransactionModelValidationError(
|
|
1565
|
-
message=_('Nothing to undo for this staged transaction.')
|
|
1566
|
-
)
|
|
1863
|
+
raise StagedTransactionModelValidationError(message=_('Nothing to undo for this staged transaction.'))
|
|
1567
1864
|
|
|
1568
1865
|
def clean(self, verify: bool = False):
|
|
1569
1866
|
if self.has_children():
|
|
@@ -1579,14 +1876,26 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1579
1876
|
if not self.can_have_activity():
|
|
1580
1877
|
self.activity = None
|
|
1581
1878
|
|
|
1879
|
+
if self.is_sales():
|
|
1880
|
+
self.vendor_model = None
|
|
1881
|
+
|
|
1882
|
+
if self.is_expense():
|
|
1883
|
+
self.customer_model = None
|
|
1884
|
+
|
|
1885
|
+
if self.is_children() and self.is_bundled():
|
|
1886
|
+
self.vendor_model = None
|
|
1887
|
+
self.customer_model = None
|
|
1888
|
+
|
|
1582
1889
|
if self.is_children():
|
|
1890
|
+
self.bundle_split = self.parent.bundle_split
|
|
1891
|
+
|
|
1892
|
+
if all([self.is_parent(), not self.is_bundled()]):
|
|
1583
1893
|
self.vendor_model = None
|
|
1584
1894
|
self.customer_model = None
|
|
1585
1895
|
|
|
1586
|
-
if
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
)
|
|
1896
|
+
if self.is_transfer():
|
|
1897
|
+
self.vendor_model = None
|
|
1898
|
+
self.customer_model = None
|
|
1590
1899
|
|
|
1591
1900
|
if verify:
|
|
1592
1901
|
self.is_role_mapping_valid(raise_exception=True)
|
|
@@ -1632,14 +1941,9 @@ def importjobmodel_presave(instance: ImportJobModel, **kwargs):
|
|
|
1632
1941
|
entity ID of the Ledger Model.
|
|
1633
1942
|
"""
|
|
1634
1943
|
if instance.is_configured():
|
|
1635
|
-
if
|
|
1636
|
-
instance.bank_account_model.entity_model_id
|
|
1637
|
-
!= instance.ledger_model.entity_id
|
|
1638
|
-
):
|
|
1944
|
+
if instance.bank_account_model.entity_model_id != instance.ledger_model.entity_id:
|
|
1639
1945
|
raise ImportJobModelValidationError(
|
|
1640
|
-
message=_(
|
|
1641
|
-
'Invalid Bank Account for LedgerModel. No matching Entity Model found.'
|
|
1642
|
-
)
|
|
1946
|
+
message=_('Invalid Bank Account for LedgerModel. No matching Entity Model found.')
|
|
1643
1947
|
)
|
|
1644
1948
|
|
|
1645
1949
|
|