def do_recharge(self, gas, amount, note="", date=None): """ Do a recharge of amount ``amount`` to the corresponding member account in the GAS ``gas``. If this person is not a member of GAS ``gas``, or if ``amount`` is a negative number a ``MalformedTransaction`` exception is raised. """ person = self.subject.instance if amount < 0: raise MalformedTransaction( ugettext("Amount of a recharge must be non-negative")) elif not person.has_been_member(gas): raise MalformedTransaction( ugettext( "A person can't make an account recharge for a GAS that (s)he is not member of" )) else: source_account = self.system['/wallet'] exit_point = self.system['/expenses/gas/' + gas.uid + '/recharges'] entry_point = gas.accounting.system['/incomes/recharges'] target_account = gas.accounting.system['/members/' + person.uid] description = unicode(person.report_name) issuer = self.subject if not date: date = datetime.now() #_date.today transaction = register_transaction(source_account, exit_point, entry_point, target_account, amount, description, issuer, date, 'RECHARGE') transaction.add_references([person, gas])
def do_recharge(self, gas, amount): """ Do a recharge of amount ``amount`` to the corresponding member account in the GAS ``gas``. If this person is not a member of GAS ``gas``, or if ``amount`` is a negative number, a ``MalformedTransaction`` exception is raised. """ person = self.subject.instance if amount < 0: raise MalformedTransaction( "Amount of a recharge must be non-negative") elif not person.is_member(gas): raise MalformedTransaction( "A person can't make an account recharge for a GAS that (s)he is not member of" ) else: source_account = self.system['/wallet'] exit_point = self.system['/expenses/gas/' + gas.uid + '/recharges'] entry_point = gas.accounting.system['/incomes/recharges'] target_account = gas.accounting.system['/members/' + person.uid] description = "GAS member account recharge" issuer = person transaction = register_transaction(source_account, exit_point, entry_point, target_account, amount, description, issuer, kind='RECHARGE') transaction.add_references([person, gas])
def extra_operation(self, gas, pact, amount, target, causal, date): """ Another account operation for this subject For a Supplier the target operation can be income or expense operation with GAS """ if amount < 0: raise MalformedTransaction(_("Payment amounts must be non-negative")) if amount < 0: raise MalformedTransaction(_("Refund amounts must be non-negative")) supplier = self.subject.instance if supplier not in gas.suppliers: msg = _("An active solidal pact must be in place between a supplier and the GAS (s)he is refunding") raise MalformedTransaction(msg) gas_acc = gas.accounting gas_system = gas.accounting.system #Correzione a favore del GAS: +GAS -fornitore if target == EXPENSE: #+GAS -Supplier #UGLY: remove me when done and executed one command that regenerate all missing accounts self.missing_accounts(gas) source_account = self.system['/wallet'] exit_point = self.system['/expenses/gas/' + gas.uid] entry_point = gas_system['/incomes/suppliers/' + supplier.uid] target_account = gas_system['/cash'] #Correzione a favore del fornitore: +fornitore -GAS elif target == INCOME: #+Supplier -GAS source_account = gas_system['/cash'] exit_point = gas_system['/expenses/suppliers/' + supplier.uid] entry_point = self.system['/incomes/gas/' + gas.uid] target_account = self.system['/wallet'] else: raise MalformedTransaction(_("Payment target %s not identified") % target) description = "%(pact)s %(target)s %(causal)s" % { 'pact': pact, 'target': target, 'causal': causal } issuer = self.subject kind = PACT_EXTRA if not date: date = datetime.now() #_date.today transaction = register_transaction(source_account, exit_point, entry_point, target_account, amount, description, issuer, date, kind)
def extra_operation(self, amount, target, causal, date): """ Another account operation for this subject For a GAS the target operation can be income or expense operation """ if amount < 0: raise MalformedTransaction( ugettext("Payment amounts must be non-negative")) gas = self.subject.instance non_des = self.get_non_des_accounting() if not non_des: raise Person.DoesNotExist non_des_system = non_des.system if target == INCOME: source_account = non_des_system['/wallet'] exit_point, created = non_des_system.get_or_create_account( '/expenses', 'OutOfDES', account_type.expense) entry_point = self.system['/incomes/OutOfDES'] target_account = self.system[ '/cash'] #WAS gas.accounting.system['/cash'] elif target == EXPENSE: source_account = self.system['/cash'] exit_point = self.system['/expenses/OutOfDES'] entry_point, created = non_des_system.get_or_create_account( '/incomes', 'OutOfDES', account_type.income) target_account = non_des_system['/wallet'] else: #WAS raise MalformedTransaction(ugettext("Payment target %s not identified" % target)) #coercing to Unicode: need string or buffer, __proxy__ found raise MalformedTransaction( ugettext("Payment target %s not identified") % target) description = "%(gas)s %(target)s %(causal)s" % { 'gas': gas.id_in_des, 'target': target, 'causal': causal } #WAS raise description = ugettext("%(gas)s %(target)s %(causal)s") % { ... #WAS exceptions must be old-style classes or derived from BaseException, not unicode issuer = self.subject kind = GAS_EXTRA if not date: date = datetime.datetime.now() #_date.today # transaction = register_simple_transaction(source_account, target_account, amount, description, issuer, date=date, kind=kind) transaction = register_transaction(source_account, exit_point, entry_point, target_account, amount, description, issuer, date, kind)
def pay_membership_fee(self, gas, year): """ Pay the annual membership fee for a GAS this person is member of. Fee amount is determined by the ``gas.membership_fee`` attribute. If this person is not a member of GAS ``gas``, a ``MalformedTransaction`` exception is raised. """ person = self.subject.instance if not person.is_member(gas): raise MalformedTransaction( "A person can't pay membership fees to a GAS that (s)he is not member of" ) source_account = self.system['/wallet'] exit_point = self.system['/expenses/gas/' + gas.uid + '/fees'] entry_point = gas.accounting.system['/incomes/fees'] target_account = gas.accounting.system['/cash'] amount = gas.membership_fee description = "Membership fee for year %(year)s" % { 'year': year, } issuer = person transaction = register_transaction(source_account, exit_point, entry_point, target_account, amount, description, issuer, kind='MEMBERSHIP_FEE') transaction.add_references([person, gas])
def pay_supplier_order(self, order): """ Register the payment of a supplier order. Specifically, such registration is a two-step process: 1. First, the GAS withdraws from each member's account an amount of money corresponding to the total cost of products (s)he bought during this order (price & quantity are as recorded by the invoice!) 2. Then, the GAS collects this money amounts and transfers them to the supplier's account If the given supplier order hasn't been fully withdrawn by GAS members yet, raise ``MalformedTransaction``. """ if order.status == GASSupplierOrder.WITHDRAWN: ## bill members for their orders to the GAS # only members participating to this order need to be billed for member in order.purchasers: # calculate amount to bill to this GAS member for orders (s)he issued # w.r.t. the given supplier order member_order_bill = 0 issued_member_orders = member.issued_orders.filter( ordered_product__order=order) for member_order in issued_member_orders: price = member_order.ordered_product.delivered_price quantity = member_order.withdrawn_amount member_order_bill += price * quantity self.withdraw_from_member_account(member, member_order_bill) ## pay supplier self.pay_supplier(pact=order.pact, amount=order.total_amount) else: raise MalformedTransaction( "Only fully withdrawn supplier orders are eligible to be payed" )
def withdraw_from_member_account(self, member, amount, refs=None): """ Withdraw a given amount ``amount`` of money from the account of a member of this GAS and bestow it to the GAS's cash. If this operation would make that member's account negative, raise a warning. If ``member`` is not a member of this GAS, a ``MalformedTransaction`` exception is raised. References for this transaction may be passed as the ``refs`` argument (e.g. a list of GAS member orders this withdrawal is related to). """ # TODO: if this operation would make member's account negative, raise a warning gas = self.subject.instance if not member.person.is_member(gas): raise MalformedTransaction( "A GAS can withdraw only from its members' accounts") source_account = self.system['/members/' + member.uid] target_account = self.system['/cash'] description = "Withdrawal from member %(member)s account by GAS %(gas)s" % { 'gas': gas, 'member': member, } issuer = gas transaction = register_simple_transaction(source_account, target_account, amount, description, issuer, date=None, kind='GAS_WITHDRAWAL') if refs: transaction.add_references(refs)
def pay_supplier(self, pact, amount, refs=None): """ Transfer a given (positive) amount ``amount`` of money from the GAS's cash to a supplier for which a solidal pact is currently active. If ``amount`` is negative, a ``MalformedTransaction`` exception is raised (supplier-to-GAS money transfers should be treated as "refunds"). References for this transaction may be passed as the ``refs`` argument (e.g. a list of supplier orders this payment is related to). """ if amount < 0: raise MalformedTransaction("Payment amounts must be non-negative") gas = self.subject.instance supplier = pact.supplier source_account = self.system['/cash'] exit_point = self.system['/expenses/suppliers/' + supplier.uid] entry_point = supplier.accounting.system['/incomes/gas/' + gas.uid] target_account = supplier.accounting.system['/wallet'] description = "Payment from GAS %(gas)s to supplier %(supplier)s" % { 'gas': gas, 'supplier': supplier, } issuer = gas transaction = register_transaction(source_account, exit_point, entry_point, target_account, amount, description, issuer, kind='PAYMENT') if refs: transaction.add_references(refs)
def register_split_transaction(source, splits, description, issuer, date=None, kind=None): """ A factory function for registering general (split) transactions between accounts. When invoked, this function takes care of the following tasks: * create a new ``Transaction`` model instance from the given input arguments * for each account involved in the transaction, add an entry to the corresponding ledger (as a ``LedgerEntry`` instance). Arguments ========= ``source`` A ``CashFlow`` model instance specifying the source account for the transaction and the amount of money flowing from/to it ``splits`` An iterable of ``Split`` model instances, representing the flow components (a.k.a. *splits*) from which the transaction is made. They must satisfy all the compatibility constraints descending from the reference accounting model (for details, see ``Transaction`` model's docstring) ``description`` A string describing what the transaction stands for ``issuer`` The economic subject (a ``Subject`` model instance) who issued the transaction ``date`` A reference date for the transaction (as a ``DateTime`` object); default to the current date & time ``kind`` A type specification for the transaction. It's an (optional) domain-specific string; if specified, it must be one of the values listed in ``settings.TRANSACTION_TYPES`` Return value ============ If input is valid, return the newly created ``Transaction`` model instance; otherwise, report to the client code whatever error(s) occurred during the processing, by raising a ``MalformedTransaction`` exception. """ try: transaction = Transaction() transaction.source = source transaction.description = description transaction.issuer = issuer transaction.date = date transaction.kind = kind transaction.save() # set transaction splits transaction.split_set = splits except ValidationError, e: err_msg = _(u"Transaction specs are invalid: %(specs)s. The following error(s) occured: %(errors)s")\ % {'specs':transaction_details(transaction), 'errors':str(e.message_dict)} raise MalformedTransaction(err_msg)
def refund_gas(self, gas, amount, refs=None): """ Refund a given ``amount`` of money to a GAS for which a solidal pact is currently active. If GAS ``gas`` doesn't have an active solidal pact with this supplier, or if ``amount`` is negative, raise a ``MalformedTransaction`` exception. References for this transaction may be passed as the ``refs`` argument (e.g. a list of supplier orders this refund is related to). """ if amount < 0: raise MalformedTransaction("Refund amounts must be non-negative") supplier = self.subject.instance if supplier not in gas.suppliers: msg = "An active solidal pact must be in place between a supplier and the GAS (s)he is refunding" raise MalformedTransaction(msg) source_account = self.system['/wallet'] exit_point = self.system['/incomes/gas/' + gas.uid] entry_point = gas.accounting.system['/expenses/suppliers/' + supplier.uid] target_account = gas.accounting.system['/cash'] description = "Refund from supplier %(supplier)s to GAS %(gas)s" % { 'gas': gas, 'supplier': supplier, } issuer = supplier transaction = register_transaction(source_account, exit_point, entry_point, target_account, amount, description, issuer, kind='REFUND') if refs: transaction.add_references(refs)
def withdraw_from_member_account(self, member, new_amount, refs, order, date=None, comment=""): """ Withdraw a given amount ``new_amount`` of money from the account of a member of this GAS and bestow it to the GAS's cash. If this operation would make that member's account negative, raise a warning. If ``member`` is not a member of this GAS, a ``MalformedTransaction`` exception is raised. References for this transaction may be passed as the ``refs`` argument (e.g. a list of GAS member orders this withdrawal is related to). """ # Only for test Control if yet exist some transaction for this refs. #computed_amount, existing_txs = self.get_amount_by_gas_member(member, order) #log.debug("ACCOUNTING %(computed_amount)s %(existing_txs)s" % {'computed_amount': computed_amount, 'existing_txs': existing_txs}) gas = self.subject.instance if member.gas != gas: raise MalformedTransaction( ugettext("A GAS can withdraw only from its members' accounts")) source_account = self.system['/members/' + member.person.uid] target_account = self.system['/cash'] #'gas': gas.id_in_des, #WAS: description = "%(person)s %(order)s" % {'person': member.person.report_name, 'order': order.report_name} #NOTE LF: person is a repetition of gasmember person bound description = order.common_name if comment: description = u"%s (%s)" % (description, comment) issuer = self.subject log.debug( "registering transaction: issuer=%s descr=%s source=%s target=%s" % (issuer, description, source_account, target_account)) transaction = register_simple_transaction( source_account, target_account, new_amount, description, issuer, date=date, kind=GasAccountingProxy.GAS_WITHDRAWAL) if refs: transaction.add_references(refs)
def get_account(self, system, parent_path, name, kind): path = parent_path + '/' + name try: account = system[path] except Exception as e: #should be KeyError, but maybe is an AttributeError due to previous implementation #WAS if not account: but raise exception "Account matching query does not exist." #if not exist create it system.add_account(parent_path=parent_path, name=name, kind=kind) account = system[path] if not account: raise MalformedTransaction( ugettext("Unknow account: %(system)s %(path)s %(kind)s") % { 'system': system, 'path': path, 'kind': kind }) return account
def pay_supplier(self, order, amount, refs=None, descr=None, date=None, multiple=None): """ Transfer a given (positive) amount ``amount`` of money from the GAS's cash to a supplier for which a solidal pact is currently active. If ``amount`` is negative, a ``MalformedTransaction`` exception is raised (supplier-to-GAS money transfers should be treated as "refunds"). References for this transaction may be passed as the ``refs`` argument (e.g. a list of supplier orders this payment is related to). """ if amount < 0: raise MalformedTransaction( ugettext(u"Payment amounts must be non-negative")) gas = self.subject.instance supplier = order.supplier source_account = self.system['/cash'] exit_point = self.system['/expenses/suppliers/' + supplier.uid] entry_point = supplier.accounting.system['/incomes/gas/' + gas.uid] target_account = supplier.accounting.system['/wallet'] if multiple: description = "Ord. %s" % multiple description += " %(pact)s" % { 'pact': order.pact, } else: description = order.common_name if descr: description += ". %s" % descr.replace(description + ". ", "") issuer = self.subject transaction = register_transaction(source_account, exit_point, entry_point, target_account, amount, description, issuer, date, 'PAYMENT') if refs: transaction.add_references(refs)
def pay_membership_fee(self, member, year, date=None): """ Pay the annual membership fee for this GAS member. Fee amount is determined by the ``gas.membership_fee`` attribute. If the gas member belongs to another ``gas``, a ``MalformedTransaction`` exception is raised. """ gas = self.subject.instance if member.gas != gas: raise MalformedTransaction( ugettext( "A person can't pay membership fees to a GAS that (s)he is not member of" )) person = member.person source_account = self.system['/members/' + person.uid] target_account = self.system['/cash'] amount = gas.membership_fee description = ugettext("year %(year)s --> %(person)s") % { 'person': person.report_name, 'year': year, } issuer = self.subject if not date: date = datetime.datetime.now() #_date.today transaction = register_simple_transaction( source_account, target_account, amount, description, issuer, date, kind=GasAccountingProxy.MEMBERSHIP_FEE) transaction.add_references([person, gas])
def register_simple_transaction(source_account, target_account, amount, description, issuer, date=None, kind=None): """ A factory function for registering simple transactions. This is just a convenience version of ``register_transaction``, to be used when dealing with simple transactions. When invoked, this function takes care of the following tasks: * create a new ``Transaction`` model instance from the given input arguments * for each account involved in the transaction (i.e., ``source`` and ``target``), add an entry to the corresponding ledger (as a ``LedgerEntry`` instance). For details about simple transactions, see ``Transaction`` model's docstring. Arguments ========= ``source_account`` the source account for the transaction (a stock-like ``Account`` model instance) ``target_account`` the target account for the transaction (a stock-like ``Account`` model instance) ``amount`` the amount of money flowing between source and target accounts (as a signed decimal); its sign determines the flows's direction with respect to the source account (i.e., positive -> outgoing, negative -> incoming) ``description`` A string describing what the transaction stands for ``issuer`` The economic subject (a ``Subject`` model instance) who issued the transaction ``date`` A reference date for the transaction (as a ``DateTime`` object); default to the current date & time ``kind`` A type specification for the transaction. It's an (optional) domain-specific string; if specified, it must be one of the values listed in ``settings.TRANSACTION_TYPES`` Return value ============ If input is valid, return the newly created ``Transaction`` model instance; otherwise, report to the client code whatever error(s) occurred during the processing, by raising a ``MalformedTransaction`` exception. """ try: transaction = Transaction() # source flow source = CashFlow.objects.create(account=source_account, amount=amount) transaction.source = source transaction.description = description transaction.issuer = issuer transaction.date = date transaction.kind = kind transaction.save() # construct the (single) transaction split from input arguments # entry- & exit- points are missing, because this is an internal transaction # target flow target = CashFlow.objects.create(account=target_account, amount=-amount) split = Split.objects.create(target=target) # add this single split to the transaction transaction.split_set = [split] except ValidationError, e: err_msg = _(u"Transaction specs are invalid: %(specs)s. The following error(s) occured: %(errors)s")\ % {'specs':transaction_details(transaction), 'errors':str(e.message_dict)} raise MalformedTransaction(err_msg)
def register_internal_transaction(source, targets, description, issuer, date=None, kind=None): """ A factory function for registering internal transactions. This is just a convenience version of ``register_split_transaction``, to be used when dealing with internal transactions. When invoked, this function takes care of the following tasks: * create a new ``Transaction`` model instance from the given input arguments * for each account involved in the transaction (i.e., ``source`` and ``targets``), add an entry to the corresponding ledger (as a ``LedgerEntry`` instance). For details about internal transactions, see ``Transaction`` model's docstring. Arguments ========= ``source`` A ``CashFlow`` model instance specifying the source account for the transaction and the amount of money flowing from/to it ``targets`` An iterable of ``CashFlow`` model instances, representing the flow components (a.k.a. splits) from which the transaction is made. Since we are dealing with an internal transaction, a split is fully defined by the target account and the amount of money flowing to/from it (so, a ``CashFlow`` rather than a ``Split`` instance). ``description`` A string describing what the transaction stands for ``issuer`` The economic subject (a ``Subject`` model instance) who issued the transaction ``date`` A reference date for the transaction (as a ``DateTime`` object); default to the current date & time ``kind`` A type specification for the transaction. It's an (optional) domain-specific string; if specified, it must be one of the values listed in ``settings.TRANSACTION_TYPES`` Return value ============ If input is valid, return the newly created ``Transaction`` model instance; otherwise, report to the client code whatever error(s) occurred during the processing, by raising a ``MalformedTransaction`` exception. """ try: transaction = Transaction() transaction.source = source transaction.description = description transaction.issuer = issuer transaction.date = date transaction.kind = kind transaction.save() # construct transaction splits from input arguments splits = [] for target in targets: # entry- & exit- points are missing, because this is an internal transaction split = Split.objects.create(target=target) splits.append(split) # set transaction splits transaction.split_set = splits except ValidationError, e: err_msg = _(u"Transaction specs are invalid: %(specs)s. The following error(s) occured: %(errors)s")\ % {'specs':transaction_details(transaction), 'errors':str(e.message_dict)} raise MalformedTransaction(err_msg)
def extra_operation(self, gas, amount, target, causal, date): """ Another account operation for this subject For a GASMEMBER the target operation can be income or expense operation The operation can implicate a GAS economic change """ if amount < 0: raise MalformedTransaction( ugettext("Payment amounts must be non-negative")) person = self.subject.instance if not person.has_been_member(gas): raise MalformedTransaction( ugettext( "A person can't pay membership fees to a GAS that (s)he is not member of" )) gas_acc = gas.accounting gas_system = gas.accounting.system kind = GASMEMBER_GAS #UGLY: remove me when done and executed one command that regenerate all missing accounts self.missing_accounts(gas) if target == INCOME: #Correction for gasmember: +gasmember -GAS source_account = gas_system['/cash'] exit_point = gas_system['/expenses/member'] entry_point = gas_system['/incomes/recharges'] target_account = gas_system['/members/' + person.uid] elif target == EXPENSE: #Correction for GAS: +GAS -gasmember source_account = gas_system['/members/' + person.uid] exit_point = gas_system['/expenses/gas'] entry_point = gas_system['/incomes/member'] target_account = gas_system['/cash'] elif target == ASSET: #Detraction for Gasmember: -gasmember source_account = gas_system['/members/' + person.uid] exit_point = gas_system['/expenses/member'] entry_point = self.system['/incomes/other'] target_account = self.system['/wallet'] kind = ADJUST elif target == LIABILITY: #Addition for Gasmember: +gasmember source_account = self.system['/wallet'] exit_point = self.system['/expenses/other'] entry_point = gas_system['/incomes/recharges'] target_account = gas_system['/members/' + person.uid] kind = ADJUST elif target == EQUITY: #Restitution for gasmember: empty container +gasmember -GAS source_account = gas_system['/cash'] exit_point = gas_system['/expenses/member'] entry_point = gas_system['/incomes/recharges'] target_account = gas_system['/members/' + person.uid] kind = RECYCLE else: raise MalformedTransaction( ugettext("Payment target %s not identified") % target) description = "%(gas)s %(target)s %(causal)s" % { 'gas': gas.id_in_des, 'target': target, 'causal': causal } issuer = self.subject if not date: date = datetime.now() #_date.today transaction = register_transaction(source_account, exit_point, entry_point, target_account, amount, description, issuer, date, kind)