Исходный код elastic.stock.models

# -*- coding: utf-8 -*-

from django.db import models
from django.utils.translation import ugettext_lazy as _
from elastic.party.models import Party
import mptt
from mptt.models import MPTTModel, TreeForeignKey, TreeManager
from django.core.exceptions import ObjectDoesNotExist
from elastic.product.models import Product
from django.db import transaction
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.conf import settings
import uuid
from elastic.product.exceptions import *
from django.template import Context, Template
from elastic.workflow.models import Workflow, Schema
from django.dispatch import receiver
from signals import workflow_restart, workflow_delete

__author__ = "Igor S. Kovalenko"
__contact__ = "kovalenko@sb-soft.biz"
__site__ = "http://www.elastic-trade-server.org"
__year__ = "2015"
__description__ = "Stock package"


@receiver(workflow_restart, dispatch_uid='workflow_restart')
def workflow_restart_handler(sender, instance, **kwargs):
    # Удаляем и заново создаем рабочий процесс (workflow) связанный с документом
    instance.get_workflow().delete()
    instance.get_workflow().start()


@receiver(workflow_delete, dispatch_uid='workflow_delete')
def workflow_delete_handler(sender, instance, **kwargs):
    # Удаляем рабочий процесс (workflow) связанный с документом
    instance.get_workflow().delete()


class StockLocationManager(TreeManager):

    @staticmethod
    def get_product_price_cost_on_location(location_code, sku):
        return StockLocation.objects.get(code=location_code).get_product_price_cost(sku)

    @staticmethod
    def get_product_rest_on_location(location_code, sku):
        return StockLocation.objects.get(code=location_code).get_product_rest(sku)


[документация]class StockLocation(MPTTModel): class Meta: verbose_name = _('Stock location') verbose_name_plural = _('Stock locations') class MPTTMeta: parent_attr = 'parent' TYPE = ( ('WH', _('Warehouse')), ('CUS', _('Customer')), ('SUP', _('Supplier')), ('IN', _('Input')), ('OUT', _('Output')), ('STO', _('Store')), ) parent = TreeForeignKey('self', verbose_name=_('Parent'), null=True, blank=True, related_name='children') code = models.CharField(verbose_name=_('Code'), max_length=128, db_index=True) name = models.CharField(max_length=255, verbose_name=_('Name')) type_location = models.CharField(max_length=3, choices=TYPE, default='WH', verbose_name=_('Type location')) party = models.ForeignKey(Party, null=True, blank=True, verbose_name=_('Party')) objects = StockLocationManager() def __unicode__(self): if self.type_location == 'WH': return self.name root = self.get_root() return u"%s/%s" % (root.name, self.name) def get_product_price_cost(self, sku): try: return PriceRegister.objects.get(location=self, product__sku=sku).actual_price_cost except ObjectDoesNotExist: return 0 def get_product_rest(self, sku): try: rest_register = RestRegister.objects.get(location=self, product__sku=sku) reserve_register = ReserveRegister.objects.get(location=self, product__sku=sku) # if product.serial_mark in ['accounted_party', 'product_party', ]: # pass # if product.serial_mark == 'none': # pass return rest_register.actual_quantity - reserve_register.actual_quantity except ObjectDoesNotExist: return 0 def save(self, *args, **kwargs): if not self.id and not self.parent: self.type_location = "WH" # Корень всегда считается складом super(StockLocation, self).save(*args, **kwargs)
mptt.register(StockLocation)
[документация]class Consignment(models.Model): """ ТОРГ-13 "Накладная на внутреннее перемещение, передачу товаров, тары" """ class Meta: verbose_name = _('Consignment') verbose_name_plural = _('Consignments') #: Номер документа doc_no = models.CharField(verbose_name=_('Doc no'), max_length=128, db_index=True) #: Внутренний код документа code = models.CharField(max_length=128, db_index=True, verbose_name=_('Code')) #: Дата/время создания creation_date = models.DateTimeField(verbose_name=_('Creation date')) #: Дата запланированного прихода planned_date = models.DateTimeField(null=True, blank=True, verbose_name=_('Planned date')) #: Дата исполнения effective_date = models.DateTimeField(null=True, blank=True, verbose_name=_('Effective date')) #: Дата/время последней модификации last_modify_date = models.DateTimeField(verbose_name=_('Last modify date')) #: Метка узла-создателя документа created_by = models.CharField(max_length=64, verbose_name=_('Node creator')) #: Откуда (наименование) from_location_name = models.CharField(max_length=255, verbose_name=_('From location name')) #: Откуда (код) from_location_code = models.CharField(max_length=128, verbose_name=_('From location code')) #: Куда (наименованеи) to_location_name = models.CharField(max_length=255, verbose_name=_('To location name')) #: Куда (код) to_location_code = models.CharField(max_length=128, verbose_name=_('To location code')) #: Примечание description = models.TextField(null=True, blank=True, verbose_name=_('Description')) def __unicode__(self): tmpl = Template("{% load tz %} {{ effective_date|localtime}}") creation_date = tmpl.render(Context({'effective_date': self.creation_date})) return u"#{0} {1} {2}".format(self.doc_no, _('at'), creation_date) def get_workflow(self): # Регистрируем/загружаем связанный поток исполнения workflow_schema = Schema.objects.get_or_create( code='goods_reception', defaults={ "name": 'Оприходование товара', "code": 'goods_reception', })[0] workflow_instance = Workflow.objects.get_or_create( doc_id=self.id, defaults={ "doc_object": self, "workflow_schema": workflow_schema, })[0] return workflow_instance
[документация] def is_original(self): """ Возвращает Истина, если документ изготовлен локальным нодом, и возвращает Ложь если документ является репликой оригинала """ return self.created_by == settings.ELASTIC_SETTINGS['server_name']
@transaction.atomic()
[документация] def hold_at_rest_register(self): """ Проводим коносамент по регистру остатков """ # Оформляем расход try: from_location = StockLocation.objects.get(code=self.from_location_code) except ObjectDoesNotExist: pass else: product_index = {product.sku: product for product in Product.objects.filter( sku__in=self.rows.values_list("product_sku", flat=True))} for row in self.rows.iterator(): try: register = RestRegister.objects.get_or_create( product__sku=row.product_sku, location=from_location, defaults={ "actual_datetime": timezone.datetime.min.replace(tzinfo=timezone.get_current_timezone()), "location": from_location, "product": product_index[row.product_sku], })[0] except IndexError: raise ProductNotExists(row.product_sku) RestRegisterRow.objects.create( effective_date=self.effective_date, quantity=-row.quantity, doc_code=self.code, short_desc=unicode(self), register=register ) # Оформляем приход try: to_location = StockLocation.objects.get(code=self.to_location_code) except ObjectDoesNotExist: pass else: product_index = {product.sku: product for product in Product.objects.filter( sku__in=self.rows.values_list("product_sku", flat=True))} for row in self.rows.iterator(): try: register = RestRegister.objects.get_or_create( product__sku=row.product_sku, location=to_location, defaults={ "actual_datetime": timezone.datetime.min.replace(tzinfo=timezone.get_current_timezone()), "location": to_location, "product": product_index[row.product_sku], })[0] except IndexError: raise ProductNotExists(row.product_sku) RestRegisterRow.objects.create( effective_date=self.effective_date, quantity=row.quantity, doc_code=self.code, short_desc=unicode(self), register=register )
@transaction.atomic()
[документация] def unhold_at_rest_register(self): """ Отмена проводки коносамента по регистру остатков """ for row in RestRegisterRow.objects.filter(doc_code=self.code): row.delete()
@transaction.atomic()
[документация] def hold_at_transit_register(self): """ Провести коносамент по регистру "Товары в пути" """ # Оформляем расход try: from_location = StockLocation.objects.get(code=self.from_location_code) except ObjectDoesNotExist: pass else: product_index = {product.sku: product for product in Product.objects.filter( sku__in=self.rows.values_list("product_sku", flat=True))} for row in self.rows.iterator(): try: register = TransitRegister.objects.get_or_create( product__sku=row.product_sku, location=from_location, defaults={ "actual_datetime": timezone.datetime.min.replace(tzinfo=timezone.get_current_timezone()), "location": from_location, "product": product_index[row.product_sku], })[0] except IndexError: raise ProductNotExists(row.product_sku) TransitRegisterRow.objects.create( effective_date=self.effective_date, quantity=-row.quantity, doc_code=self.code, short_desc=unicode(self), register=register ) # Оформляем приход try: to_location = StockLocation.objects.get(code=self.to_location_code) except ObjectDoesNotExist: pass else: product_index = {product.sku: product for product in Product.objects.filter( sku__in=self.rows.values_list("product_sku", flat=True))} for row in self.rows.iterator(): try: register = TransitRegister.objects.get_or_create( product__sku=row.product_sku, location=to_location, defaults={ "actual_datetime": timezone.datetime.min.replace(tzinfo=timezone.get_current_timezone()), "location": to_location, "product": product_index[row.product_sku], })[0] except IndexError: raise ProductNotExists(row.product_sku) TransitRegisterRow.objects.create( effective_date=self.planned_date, quantity=row.quantity, doc_code=self.code, short_desc=unicode(self), register=register )
@transaction.atomic()
[документация] def unhold_at_transit_register(self): """ Отменить проводку коносамента по регистру "Товары в пути" """ for row in TransitRegisterRow.objects.filter(doc_code=self.code): row.delete()
def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.code: self.code = uuid.uuid1() # Устанавливаем дату создания if not self.creation_date: self.creation_date = timezone.now() # Устанавливаем дату последней модификации if not self.last_modify_date: self.last_modify_date = timezone.now() # Устанавливаем имя локального нода, в качестве нода-создателя документа if not self.created_by: self.created_by = settings.ELASTIC_SETTINGS['server_name'] if not self.doc_no: # Устанавливаем номер документа s = Sequence.objects.get_or_create( name='consignment', defaults={ 'name': 'consignment', 'prefix': 'CON', 'separator': '-', } )[0] self.doc_no = s.request_next_number() super(Consignment, self).save(force_insert, force_update, using, update_fields)
[документация]class ConsignmentRow(models.Model): """ Табличная часть ТОРГ-13 "Накладная на внутреннее перемещение, передачу товаров, тары" """ class Meta: verbose_name = _('Consignment row') verbose_name_plural = _('Consignment rows') product = models.CharField(max_length=1024, verbose_name=_('Product name')) product_sku = models.CharField(verbose_name=_('Product SKU'), max_length=128) quantity = models.DecimalField(max_digits=12, decimal_places=3, verbose_name=_('Quantity')) price_cost = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True, default=0, verbose_name=_('Price cost')) total_cost = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True, default=0, verbose_name=_('Total cost')) consignment = models.ForeignKey(Consignment, related_name='rows', verbose_name=_('Consignment')) def __unicode__(self): return u"{0} - {1}".format(self.product_sku, self.product)
[документация]class RevaluationAct(models.Model): """ Акт переоценки товара """ class Meta: verbose_name = _('Act of revaluation') verbose_name_plural = _("Acts of revaluation") #: Номер документа doc_no = models.CharField(verbose_name=_('Doc no'), max_length=128, db_index=True) #: Внутренний код документа code = models.CharField(max_length=128, db_index=True, verbose_name=_('Code')) #: Дата/время создания creation_date = models.DateTimeField(verbose_name=_('Creation date')) #: Дата исполнения effective_date = models.DateTimeField(null=True, blank=True, verbose_name=_('Effective date')) #: Дата/время последней модификации last_modify_date = models.DateTimeField(verbose_name=_('Last modify date')) #: Метка узла-создателя документа created_by = models.CharField(max_length=64, verbose_name=_('Node creator')) location_name = models.CharField(max_length=255, verbose_name=_('Location name')) location_code = models.CharField(max_length=128, verbose_name=_('Location code')) description = models.TextField(null=True, blank=True, verbose_name=_('Description')) def __unicode__(self): tmpl = Template("{% load tz %} {{ effective_date|localtime}}") creation_date = tmpl.render(Context({'effective_date': self.creation_date})) return u"#{0} {1} {2}".format(self.doc_no, _('at'), creation_date) def get_workflow(self): # Регистрируем/загружаем связанный поток исполнения workflow_schema = Schema.objects.get_or_create( code='goods_revaluation', defaults={ "name": 'Переоценка товара', "code": 'goods_revaluation', })[0] workflow_instance = Workflow.objects.get_or_create( doc_id=self.id, defaults={ "doc_object": self, "workflow_schema": workflow_schema, })[0] return workflow_instance
[документация] def is_original(self): """ Возвращает Истина, если документ изготовлен локальным нодом, и возвращает Ложь если документ является репликой оригинала """ return self.created_by == settings.ELASTIC_SETTINGS['server_name']
@transaction.atomic()
[документация] def hold_at_price_register(self): """ Проводим акт переоценки товара устанавливая цены на регистрах """ try: location = StockLocation.objects.get(code=self.location_code) except ObjectDoesNotExist: pass else: product_index = {product.sku: product for product in Product.objects.filter( sku__in=self.rows.values_list("product_sku", flat=True))} for row in self.rows.iterator(): try: register = PriceRegister.objects.get_or_create( product__sku=row.product_sku, location=location, defaults={ "actual_datetime": timezone.datetime.min.replace(tzinfo=timezone.get_current_timezone()), "location": location, "product": product_index[row.product_sku], })[0] except IndexError: raise ProductNotExists(row.product_sku) PriceRegisterRow.objects.create( effective_date=self.effective_date, price_cost=row.price_cost, doc_code=self.code, short_desc=unicode(self), register=register )
@transaction.atomic()
[документация] def unhold_at_price_register(self): """ Отмена проводки акта переоценки товара """ for row in PriceRegisterRow.objects.filter(doc_code=self.code): row.delete()
def save(self, force_insert=False, force_update=False, using=None, update_fields=None): # Устанавливаем дату создания и дату последней модификации if not self.creation_date: self.creation_date = timezone.now() if not self.last_modify_date: self.last_modify_date = timezone.now() if not self.code: self.code = uuid.uuid1() if not self.doc_no: # Устанавливаем номер документа s = Sequence.objects.get_or_create( name='revaluation_act', defaults={ 'name': 'revaluation_act', 'prefix': 'RVL', 'separator': '-', } )[0] self.doc_no = s.request_next_number() super(RevaluationAct, self).save(force_insert, force_update, using, update_fields)
[документация]class RevaluationActRow(models.Model): """ Строка табличной части Акта переоценки товара """ class Meta: verbose_name = _('Row of act of revaluation') verbose_name_plural = _('Rows of act of revaluation') product = models.CharField(max_length=1024, verbose_name=_('Product name')) product_sku = models.CharField(verbose_name=_('Product SKU'), max_length=128) price_cost = models.DecimalField(max_digits=15, decimal_places=2, verbose_name=_('Price cost')) revaluation_act = models.ForeignKey(RevaluationAct, related_name='rows', verbose_name=_('Act of revaluation')) def __unicode__(self): return u"{0} - {1}".format(self.product_sku, self.product)
class RestRegister(models.Model): """ Регистр остатков. Предназначен для сохранения стоимости и остатков товара на конкретном месте хранения """ class Meta: verbose_name = _('Rest register') verbose_name_plural = _('Rest registers') actual_datetime = models.DateTimeField(verbose_name=_('Actual date and time')) actual_quantity = models.DecimalField(max_digits=12, decimal_places=3, null=True, blank=False, default=0, verbose_name=_('Actual quantity')) location = models.ForeignKey(StockLocation, limit_choices_to={"type_location": 'WH'}, verbose_name=_('Stock location')) product = models.ForeignKey(Product, verbose_name=_('Product')) def product_sku(self): return unicode(self.product.sku) product_sku.short_description = _('SKU') def manufacturer(self): return unicode(self.product.manufacturer) manufacturer.short_description = _('Manufacturer') def __unicode__(self): return unicode(self.product) class RestRegisterRow(models.Model): """ Строка регистра остатков. """ class Meta: verbose_name = _('Rest register row') verbose_name_plural = _('Rest register rows') #: Дата/время создания creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('Creation date')) #: Дата вступления в силу effective_date = models.DateTimeField(verbose_name=_('Effective date')) #: Количество quantity = models.DecimalField(max_digits=15, decimal_places=2, verbose_name=_('Quantity')) #: Номер транзакции transaction_no = models.CharField(max_length=128, db_index=True, verbose_name=_('Transaction No')) #: Внутренний код документа doc_code = models.CharField(max_length=128, db_index=True, verbose_name=_('Document internal code')) #: Краткое примечание short_desc = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Short description')) # Регистр остатка register = models.ForeignKey(RestRegister, related_name='rows', verbose_name=_('Register')) def __unicode__(self): return self.transaction_no @transaction.atomic def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.transaction_no: # Устанавливаем номер транзакции s = Sequence.objects.get_or_create( name='rest_register', defaults={ 'name': 'rest_register', 'prefix': '{0}-TR'.format(settings.ELASTIC_SETTINGS['server_name']), 'number': 1, 'separator': '-', } )[0] self.transaction_no = s.request_next_number() flag = not bool(self.pk) super(RestRegisterRow, self).save(force_insert, force_update, using, update_fields) if flag: RestRegister.objects.filter(pk=self.register_id).select_for_update().update( actual_quantity=models.F('actual_quantity') + self.quantity, actual_datetime=RestRegisterRow.objects.filter( register=self.register).order_by('-effective_date').first().effective_date ) @transaction.atomic def delete(self, using=None): RestRegister.objects.filter(id=self.register_id).select_for_update().update( actual_quantity=models.F('actual_quantity') - self.quantity ) super(RestRegisterRow, self).delete(using) # Если в регистре больше больше нет строк.... if RestRegisterRow.objects.filter(register=self.register).count() == 0: self.register.delete() # ...тогда удаляем регистр class PriceRegister(models.Model): """ Регистр цен. Предназначен для сохранения цен на товар на конкретном месте хранения """ class Meta: verbose_name = _('Price register') verbose_name_plural = _('Price registers') actual_datetime = models.DateTimeField(verbose_name=_('Actual date and time')) actual_price_cost = models.DecimalField(max_digits=15, decimal_places=2, null=True, blank=True, verbose_name=_('Actual price cost')) location = models.ForeignKey(StockLocation, limit_choices_to={"type_location": 'WH'}, verbose_name=_('Stock location')) product = models.ForeignKey(Product, verbose_name=_('Product')) def product_sku(self): return unicode(self.product.sku) product_sku.short_description = _('SKU') def manufacturer(self): return unicode(self.product.manufacturer) manufacturer.short_description = _('Manufacturer') def __unicode__(self): return unicode(self.product) class PriceRegisterRow(models.Model): """ Строка регистра цен. """ class Meta: verbose_name = _('Price register row') verbose_name_plural = _('Price register rows') #: Дата/время создания creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('Creation date')) #: Дата вступления в силу effective_date = models.DateTimeField(verbose_name=_('Effective date')) #: Продажная цена price_cost = models.DecimalField(max_digits=15, decimal_places=2, null=True, blank=True, verbose_name=_('Price cost')) #: Регистр register = models.ForeignKey(PriceRegister, related_name='rows', verbose_name=_('Register')) #: Номер транзакции transaction_no = models.CharField(max_length=128, db_index=True, verbose_name=_('Transaction No')) #: Внутренний код документа doc_code = models.CharField(max_length=128, db_index=True, verbose_name=_('Document internal code')) #: Краткое примечание short_desc = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Short description')) def __unicode__(self): return self.transaction_no @transaction.atomic def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.transaction_no: # Устанавливаем номер транзакции s = Sequence.objects.get_or_create( name='price_register', defaults={ 'name': 'price_register', 'prefix': '{0}-TR'.format(settings.ELASTIC_SETTINGS['server_name']), 'number': 1, 'separator': '-', } )[0] self.transaction_no = s.request_next_number() flag = not bool(self.pk) super(PriceRegisterRow, self).save(force_insert, force_update, using, update_fields) if flag: effective_date = PriceRegisterRow.objects.filter( register=self.register).order_by('-effective_date').first().effective_date PriceRegister.objects.filter(pk=self.register_id).select_for_update().update( actual_price_cost=self.price_cost if self.effective_date >= effective_date else models.F('actual_price_cost'), actual_datetime=effective_date ) @transaction.atomic def delete(self, using=None): super(PriceRegisterRow, self).delete(using) # Если в регистре еще есть строки, # то из первой из них (с установленной ценой) берем актуальную цену и дату first_row = PriceRegisterRow.objects.filter( register=self.register, price_cost__gt=0).order_by("-effective_date").first() if first_row: PriceRegister.objects.filter(id=self.register_id).select_for_update().update( actual_datetime=first_row.effective_date, actual_price_cost=first_row.price_cost ) else: self.register.delete() class ReserveRegister(models.Model): """ Регистр резерва. Предназначен для сохранения информации о резервах """ class Meta: verbose_name = _('Reserve register') verbose_name_plural = _('Reserve registers') actual_datetime = models.DateTimeField(verbose_name=_('Actual date and time')) actual_quantity = models.DecimalField(max_digits=12, decimal_places=3, null=True, blank=False, default=0, verbose_name=_('Actual quantity')) location = models.ForeignKey(StockLocation, limit_choices_to={"type_location": 'WH'}, verbose_name=_('Stock location')) product = models.ForeignKey(Product, verbose_name=_('Product')) def product_sku(self): return unicode(self.product.sku) product_sku.short_description = _('SKU') def manufacturer(self): return unicode(self.product.manufacturer) manufacturer.short_description = _('Manufacturer') def __unicode__(self): return unicode(self.product) class ReserveRegisterRow(models.Model): """ Строка регистра резерва """ class Meta: verbose_name = _('Reserve register row') verbose_name_plural = _('Reserve register rows') #: Дата/время создания creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('Creation date')) #: Дата вступления в силу effective_date = models.DateTimeField(verbose_name=_('Effective date')) #: Количество quantity = models.DecimalField(max_digits=15, decimal_places=2, verbose_name=_('Quantity')) #: Номер транзакции transaction_no = models.CharField(max_length=128, db_index=True, verbose_name=_('Transaction No')) #: Внутренний код документа doc_code = models.CharField(max_length=128, db_index=True, verbose_name=_('Document internal code')) #: Краткое примечание short_desc = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Short description')) # Регистр остатка register = models.ForeignKey(ReserveRegister, related_name='rows', verbose_name=_('Register')) def __unicode__(self): return self.transaction_no @transaction.atomic def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.transaction_no: # Устанавливаем номер транзакции s = Sequence.objects.get_or_create( name='reserve_register', defaults={ 'name': 'reserve_register', 'prefix': '{0}-TR'.format(settings.ELASTIC_SETTINGS['server_name']), 'number': 1, 'separator': '-', } )[0] self.transaction_no = s.request_next_number() flag = not bool(self.pk) super(ReserveRegisterRow, self).save(force_insert, force_update, using, update_fields) if flag: ReserveRegister.objects.filter(pk=self.register_id).select_for_update().update( actual_quantity=models.F('actual_quantity') + self.quantity, actual_datetime=ReserveRegisterRow.objects.filter( register=self.register).order_by('-effective_date').first().effective_date ) @transaction.atomic def delete(self, using=None): ReserveRegister.objects.filter(id=self.register_id).select_for_update().update( actual_quantity=models.F('actual_quantity') - self.quantity ) super(ReserveRegisterRow, self).delete(using) # Если в регистре больше больше нет строк.... if ReserveRegisterRow.objects.filter(register=self.register).count() == 0: self.register.delete() # ...тогда удаляем регистр class TransitRegister(models.Model): """ Регистр "Товары в пути". Предназначен отслеживания товаров, поступление которых мы ждем """ class Meta: verbose_name = _('Transit register') verbose_name_plural = _('Transit registers') actual_datetime = models.DateTimeField(verbose_name=_('Actual date and time')) actual_quantity = models.DecimalField(max_digits=12, decimal_places=3, null=True, blank=False, default=0, verbose_name=_('Actual quantity')) location = models.ForeignKey(StockLocation, limit_choices_to={"type_location": 'WH'}, verbose_name=_('Stock location')) product = models.ForeignKey(Product, verbose_name=_('Product')) def product_sku(self): return unicode(self.product.sku) product_sku.short_description = _('SKU') def manufacturer(self): return unicode(self.product.manufacturer) manufacturer.short_description = _('Manufacturer') def __unicode__(self): return unicode(self.product) class TransitRegisterRow(models.Model): """ Строка регистра "Товары в пути". """ class Meta: verbose_name = _('Transit register row') verbose_name_plural = _('Transit register rows') #: Дата/время создания creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('Creation date')) #: Дата вступления в силу effective_date = models.DateTimeField(verbose_name=_('Effective date')) #: Количество quantity = models.DecimalField(max_digits=15, decimal_places=2, verbose_name=_('Quantity')) #: Номер транзакции transaction_no = models.CharField(max_length=128, db_index=True, verbose_name=_('Transaction No')) #: Внутренний код документа doc_code = models.CharField(max_length=128, db_index=True, verbose_name=_('Document internal code')) #: Краткое примечание short_desc = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Short description')) # Регистр остатка register = models.ForeignKey(TransitRegister, related_name='rows', verbose_name=_('Register')) def __unicode__(self): return self.transaction_no @transaction.atomic def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.transaction_no: # Устанавливаем номер транзакции s = Sequence.objects.get_or_create( name='transit_register', defaults={ 'name': 'transit_register', 'prefix': '{0}-TR'.format(settings.ELASTIC_SETTINGS['server_name']), 'number': 1, 'separator': '-', } )[0] self.transaction_no = s.request_next_number() flag = not bool(self.pk) super(TransitRegisterRow, self).save(force_insert, force_update, using, update_fields) if flag: TransitRegister.objects.filter(pk=self.register_id).select_for_update().update( actual_quantity=models.F('actual_quantity') + self.quantity, actual_datetime=TransitRegisterRow.objects.filter( register=self.register).order_by('-effective_date').first().effective_date ) @transaction.atomic def delete(self, using=None): TransitRegister.objects.filter(id=self.register_id).select_for_update().update( actual_quantity=models.F('actual_quantity') - self.quantity ) super(TransitRegisterRow, self).delete(using) # Если в регистре больше больше нет строк.... if TransitRegisterRow.objects.filter(register=self.register).count() == 0: self.register.delete() # ...тогда удаляем регистр class Sequence(models.Model): class Meta: verbose_name = _('Sequence') verbose_name_plural = _('Sequences') name = models.CharField(max_length=255, verbose_name=_('Name')) separator = models.CharField(default='-', blank=True, max_length=1, verbose_name=_('Separator')) prefix = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Prefix')) number = models.IntegerField(editable=False, default=0, verbose_name=_('Number')) suffix = models.CharField(max_length=8, null=True, blank=True, verbose_name=_('Suffix')) @transaction.atomic def request_next_number(self): s = Sequence.objects.select_for_update().get(id=self.id) return s.get_next_number() def current_number(self): if self.prefix and self.suffix: return u"{0}{3}{1}{3}{2}".format(self.prefix, self.number, self.suffix, self.separator) if self.prefix: return u"{0}{2}{1}".format(self.prefix, self.number, self.separator) if self.suffix: return u"{0}{2}{1}".format(self.number, self.suffix, self.separator) return u"{0}".format(self.number) current_number.short_description = _('Current number') def get_next_number(self, force_insert=False, force_update=False, using=None): self.number += 1 super(Sequence, self).save(force_insert, force_update, using) return self.current_number() def reset_sequence(self, force_insert=False, force_update=False, using=None): self.number = 0 super(Sequence, self).save(force_insert, force_update, using) def __unicode__(self): return self.name