from trytond.pool import Pool, PoolMeta from trytond.model import ModelView, ModelSQL, fields from trytond.modules.currency.fields import Monetary from trytond.pyson import Eval, Bool, If, Get, Equal from decimal import Decimal from trytond.modules.product import price_digits from trytond.transaction import Transaction from trytond.model import Workflow from trytond.modules.company.model import ( employee_field, set_employee, reset_employee) from trytond.exceptions import UserError from trytond.wizard import ( Button, StateAction, StateTransition, StateView, Wizard) class Sale(metaclass=PoolMeta): 'Sale' __name__ = 'sale.sale' quote_number = fields.Char("Quote Number", readonly=True) sale_type = fields.Selection([('maintenance', 'Maintenance'), ('equipments', 'Equipments'), ('replaces', 'Replaces')], "Sale Type", required=True, states={'readonly': Eval('state') != 'draft'}) maintenance_type = fields.Selection([('', ""), ('preventive', 'Preventive'), ('corrective', 'Corrective') ], "Maintenance Type", states={ 'invisible': Eval('sale_type') != "maintenance", 'required': Eval('sale_type') == "maintenance", 'readonly': Eval('state') != 'draft'}, depends=['sale_type']) #contract_ref = fields.Reference("Contract Base", selection='get_origin_contract', #domain=[('party', '=', Eval('party')), # ('state', '=', 'closed')], # states={'invisible': (Eval('sale_type') != 'maintenance')}, # search_context={ # 'related_party': Eval('party'), # },) agended = fields.Boolean("Scheduling",states={ 'invisible': (Eval('sale_type') != 'maintenance'), 'readonly': True}) @classmethod def __setup__(cls): super(Sale, cls).__setup__() cls.contact.states['required']=True cls.description.states['required']=True cls.sale_date.states['required']=True cls.payment_term.states['required']=True cls._buttons.update({ 'draft': { 'invisible': (Eval('state').in_( ['cancelled', 'draft'])) | (Eval('shipment_state') == 'sent')}}) cls._transitions |= set(( ('draft', 'quotation'), ('quotation', 'confirmed'), ('confirmed', 'processing'), ('confirmed', 'draft'), ('processing', 'processing'), ('processing', 'done'), ('done', 'processing'), ('draft', 'cancelled'), ('quotation', 'cancelled'), ('quotation', 'draft'), ('cancelled', 'draft'), ('processing', 'draft') )) @fields.depends('lines', 'sale_type', 'agended') def on_chage_sale_type(self): self.lines= [] if self.sale_type != "maintenance": self.agended = False @classmethod def default_agended(self): return False # @classmethod # def _get_origin_contract(cls): # 'Return list of Model names for origin Reference' # pool = Pool() # #Contract = pool.get('optical_equipment.contract') # #return [Contract.__name__] # @classmethod # def get_origin_contract(cls): # Model = Pool().get('ir.model') # get_name = Model.get_name # models = cls._get_origin_contract() # return [(None, '')] + [(m, get_name(m)) for m in models] # def _get_shipment_sale(self, Shipment, key): # values = { # 'customer': self.shipment_party or self.party, # 'delivery_address': self.shipment_address, # 'company': self.company, # 'sale_type': self.sale_type, # 'service_maintenance_initial': True if self.sale_type != 'equipments' else False, # } # values.update(dict(key)) # return Shipment(**values) @classmethod def set_quote_number(cls, sales): ''' Fill the number field with the sale sequence ''' pool = Pool() Config = pool.get('optical_equipment.configuration') config = Config(1) for sale in sales: if config.equipment_sequence != None: if not sale.quote_number: try: sale.quote_number = config.sale_quote_number.get() cls.save(sales) except UserError: raise UserError(str('Validation Error')) else: raise UserError(gettext('optical_equipment.msg_not_sequence_quote')) @classmethod def copy(cls, sales, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('number', None) default.setdefault('invoice_state', 'none') default.setdefault('invoices_ignored', None) default.setdefault('moves', None) default.setdefault('shipment_state', 'none') default.setdefault('quoted_by') default.setdefault('confirmed_by') return super(Sale, cls).copy(sales, default=default) @classmethod @ModelView.button @Workflow.transition('quotation') def quote(cls, sales): pool = Pool() AdvancePaymentCondition = pool.get('sale.advance_payment.condition') for sale in sales: sale.check_for_quotation() cls.set_quote_number(sales) for sale in sales: sale.set_advance_payment_term() cls.save(sales) @classmethod @ModelView.button @Workflow.transition('confirmed') @set_employee('confirmed_by') def confirm(cls, sales): pool = Pool() Configuration = pool.get('sale.configuration') transaction = Transaction() context = transaction.context cls.set_sale_date(sales) cls.store_cache(sales) config = Configuration(1) MaintenanceService = pool.get('optical_equipment_maintenance.service') for sale in sales: if sale.sale_type == 'maintenance': for line in sale.lines: maintenanceService = MaintenanceService( description=sale.description, maintenance_type=sale.maintenance_type, state_agended='no_agenda', propietary=sale.party, propietary_address=sale.shipment_address, #contract_origin=sale.contract_ref if sale.contract_ref else None, sale_origin=line, sale_date=sale.sale_date, state="draft" ) maintenanceService.save() sale.agended = True sale.save() cls.set_number(sales) with transaction.set_context( queue_scheduled_at=config.sale_process_after, queue_batch=context.get('queue_batch', True)): cls.__queue__.process(sales) class SaleLine(metaclass=PoolMeta): 'SaleLine' __name__ = 'sale.line' product_equipment = fields.Boolean("Product Equipment") #equipment = fields.Many2One('optical_equipment.equipment', "Equipment", # domain=[('state', '=', 'registred'), # ('product','=', Eval('product')) # ], # states={'invisible': If(~Eval('product_equipment'), True)},) unit_digits = fields.Function(fields.Integer('Unit Digits'), 'on_change_with_unit_digits') @classmethod def __setup__(cls): super(SaleLine, cls).__setup__() cls.product.domain.append( If(Eval('_parent_sale.sale_type') == 'maintenance', [('type', '=', 'service'), ('maintenance_activity', '=', True)], [])) cls.product.domain.append(If(Eval('_parent_sale.sale_type') == 'replaces', [('replacement', '=', True)], [])) def on_change_with_unit_digits(self, name=None): if self.unit: return self.unit.digits return 2 @fields.depends('product', 'unit', 'quantity', 'sale', '_parent_sale.party', '_parent_sale.sale_type', methods=['_get_tax_rule_pattern', '_get_context_sale_price','on_change_with_amount']) def on_change_product(self): Product = Pool().get('product.product') if not self.product: self.product_equipment = False self.unit = None self.quantity = None return else: party = None if self.sale.sale_type == 'equipments': self.quantity = 1 if self.sale and self.sale.party: self.product_equipment = False party = self.sale.party # Set taxes before unit_price to have taxes in context of sale price taxes = [] pattern = self._get_tax_rule_pattern() for tax in self.product.customer_taxes_used: if party and party.customer_tax_rule: tax_ids = party.customer_tax_rule.apply(tax, pattern) if tax_ids: taxes.extend(tax_ids) continue taxes.append(tax.id) if party and party.customer_tax_rule: tax_ids = party.customer_tax_rule.apply(None, pattern) if tax_ids: taxes.extend(tax_ids) self.taxes = taxes category = self.product.sale_uom.category if not self.unit or self.unit.category != category: self.unit = self.product.sale_uom self.unit_digits = self.product.sale_uom.digits with Transaction().set_context(self._get_context_sale_price()): self.unit_price = Product.get_sale_price([self.product], self.quantity or 0)[self.product.id] if self.unit_price: self.unit_price = self.unit_price.quantize( Decimal(1) / 10 ** self.__class__.unit_price.digits[1]) self.type = 'line' self.amount = self.on_change_with_amount() if self.product.equipment: self.product_equipment = True def get_move(self, shipment_type): ''' Return moves for the sale line according to shipment_type ''' pool = Pool() Move = pool.get('stock.move') if self.type != 'line': return if not self.product: return if self.product.type not in Move.get_product_types(): return if (shipment_type == 'out') != (self.quantity >= 0): return quantity = (self._get_move_quantity(shipment_type) - self._get_shipped_quantity(shipment_type)) quantity = self.unit.round(quantity) if quantity <= 0: return if not self.sale.party.customer_location: raise PartyLocationError( gettext('sale.msg_sale_customer_location_required', sale=self.sale.rec_name, party=self.sale.party.rec_name)) move = Move() move.quantity = quantity move.uom = self.unit move.product = self.product move.from_location = self.from_location move.to_location = self.to_location move.state = 'draft' move.company = self.sale.company if move.on_change_with_unit_price_required(): move.unit_price = self.unit_price move.currency = self.sale.currency move.planned_date = self.planned_shipping_date move.invoice_lines = self._get_move_invoice_lines(shipment_type) move.origin = self return move