diff --git a/__init__.py b/__init__.py index e4641a2..888d1a1 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ from trytond.pool import Pool from . import (address, diary, party, product, purchase, sale, - equipment, configuration_equipment, maintenance, subscription, exceptions) + equipment, configuration_equipment, maintenance, move, subscription, exceptions) def register(): Pool.register( @@ -12,6 +12,7 @@ def register(): purchase.Purchase, purchase.Line, sale.Sale, + sale.SaleLine, sale.CreateSubscriptionStart, equipment.OpticalEquipment, equipment.EquipmentMaintenance, @@ -19,11 +20,11 @@ def register(): maintenance.Maintenance, maintenance.MaintenanceActivity, maintenance.MaintenanceLine, + move.Move, + move.ShipmentOut, subscription.Subscription, subscription.SubscriptionEquipment, - #subscription.CreateContractInvoiceStart, module='optical_equipment', type_='model') Pool.register( sale.CreateSubscription, - #subscription.CreateContractInvoice, module='optical_equipment', type_='wizard') diff --git a/equipment.py b/equipment.py index bf88cfb..bcab925 100644 --- a/equipment.py +++ b/equipment.py @@ -104,6 +104,10 @@ class OpticalEquipment(DeactivableMixin, Workflow, ModelSQL, ModelView): purchase_origin = fields.Reference("Purchase Origin", selection='get_origin',select=True, states={'readonly': True}) + sale_destination = fields.Reference("Sale Destination", selection='get_destination',select=True, + states={'readonly': True}) + + del _states_serial, _states, _depends @classmethod @@ -129,6 +133,24 @@ class OpticalEquipment(DeactivableMixin, Workflow, ModelSQL, ModelView): return [(None, '')] + [(m, get_name(m)) for m in models] + @classmethod + def _get_destination(cls): + 'Return list of Model names for origin Reference' + pool = Pool() + Sale = pool.get('sale.line') + + return [Sale.__name__] + + @classmethod + def get_destination(cls): + Model = Pool().get('ir.model') + get_name = Model.get_name + models = cls._get_destination() + + return [(None, '')] + [(m, get_name(m)) for m in models] + + + @classmethod def __setup__(cls): @@ -142,7 +164,7 @@ class OpticalEquipment(DeactivableMixin, Workflow, ModelSQL, ModelView): # 'draft': { # 'invisible': Eval('state') == 'draft'}, 'registred': { - 'invisible': Eval('state').in_(['registred', 'contrated'])}} + 'invisible': Eval('state').in_(['registred', 'uncontrated', 'contrated'])}} ) @classmethod diff --git a/move.py b/move.py new file mode 100644 index 0000000..cc003df --- /dev/null +++ b/move.py @@ -0,0 +1,53 @@ +from trytond.model import fields +from trytond.pool import Pool, PoolMeta + +class Move(metaclass=PoolMeta): + "Stock Move" + __name__ = "stock.move" + + serial = fields.Char('Serial') + + + +class ShipmentOut(metaclass=PoolMeta): + "Customer Shipment" + __name__ = 'stock.shipment.out' + + def _get_inventory_move(self, move): + 'Return inventory move for the outgoing move if necessary' + pool = Pool() + Move = pool.get('stock.move') + Uom = pool.get('product.uom') + quantity = move.quantity + + for inventory_move in self.inventory_moves: + if (inventory_move.origin == move + and inventory_move.state != 'cancelled'): + quantity -= Uom.compute_qty( + inventory_move.uom, inventory_move.quantity, move.uom) + quantity = move.uom.round(quantity) + + if quantity <= 0: + return + + inventory_move = Move( + from_location=self.warehouse_storage, + to_location=move.from_location, + product=move.product, + serial=move.serial, + uom=move.uom, + quantity=quantity, + shipment=self, + planned_date=move.planned_date, + company=move.company, + origin=move, + state='staging' if move.state == 'staging' else 'draft', + ) + + if inventory_move.on_change_with_unit_price_required(): + + inventory_move.unit_price = move.unit_price + + inventory_move.currency = move.currency + + return inventory_move diff --git a/move.xml b/move.xml new file mode 100644 index 0000000..3b220a6 --- /dev/null +++ b/move.xml @@ -0,0 +1,14 @@ + + + + + stock.move + + move_list_shipment + + + stock.move + + move_form + + diff --git a/sale.py b/sale.py index 76b714a..d4f24f5 100644 --- a/sale.py +++ b/sale.py @@ -1,7 +1,7 @@ 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 +from trytond.pyson import Eval, Bool, If, Get from decimal import Decimal from trytond.modules.product import price_digits from trytond.transaction import Transaction @@ -18,6 +18,7 @@ class Sale(metaclass=PoolMeta): 'Sale' __name__ = 'sale.sale' + @classmethod @ModelView.button @Workflow.transition('confirmed') @@ -39,13 +40,24 @@ class Sale(metaclass=PoolMeta): equipment.propietary=sale.party.id equipment.propietary_address=sale.shipment_address.id equipment.state="uncontrated" + equipment.sale_destination = line + equipment.maintenance_frequency = "6" if sale.party.client_type == "ips" else "12" equipment.save() with Transaction().set_context( queue_name='sale', queue_scheduled_at=config.sale_process_after): cls.__queue__.process(sales) - + + # @classmethod + # def get_equipments_in_lines(self, sales, equipments): + # #raise UserError(str(equipments)) + # equipments = [] + # for line in sales[0].lines: + # if line.product_equipment: + # equipments.append(line.equipment.id) + + # return equipments class SaleLine(metaclass=PoolMeta): 'SaleLine' @@ -56,9 +68,21 @@ class SaleLine(metaclass=PoolMeta): domain=[('state', '=', 'registred'), ('product','=', Eval('product'))], states={'invisible': If(~Eval('product_equipment'), True)},) + equipment_serial = fields.Char('Serial',states={'readonly': True, + 'invisible': If(~Eval('product_equipment'), True)}, + depends=['product_equipment']) address_equipment = fields.Many2One('party.address', "Direccion") unit_digits = fields.Function(fields.Integer('Unit Digits'), 'on_change_with_unit_digits') + + @fields.depends('product_equipment','equipment') + def get_serial_equipment(self): + if self.product_equipment: + raise UserError(str(self.equipment.serial)) + return self.equipment.serial + else: + raise UserError(str(self.equipment.serial)) + return None def on_change_with_unit_digits(self, name=None): if self.unit: @@ -70,6 +94,7 @@ class SaleLine(metaclass=PoolMeta): if self.equipment: self.product = self.equipment.product.id self.address_equipment = self.sale.shipment_address.id + self.equipment_serial = self.equipment.serial self.on_change_product() else: self.address_equipment = None @@ -78,6 +103,7 @@ class SaleLine(metaclass=PoolMeta): self.quantity = None self.unit_price = None self.amount = None + self.equipment_serial = None self.on_change_product() @fields.depends('product_equipment', methods=['on_change_equipment']) @@ -98,45 +124,105 @@ class SaleLine(metaclass=PoolMeta): self.unit = None return - party = None + else: + party = None - if self.sale and self.sale.party: - self.product_equipment = False - party = self.sale.party + 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) - # 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) + tax_ids = party.customer_tax_rule.apply(None, pattern) if tax_ids: taxes.extend(tax_ids) - continue - taxes.append(tax.id) + self.taxes = taxes - 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 - 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] - 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]) - 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() - self.type = 'line' - self.amount = self.on_change_with_amount() - self.product_equipment = True + 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 + move.serial = self.equipment_serial + + 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 @classmethod @ModelView.button diff --git a/sale.xml b/sale.xml index 9266f01..f24d340 100644 --- a/sale.xml +++ b/sale.xml @@ -6,6 +6,16 @@ sale_line_form + + sale.line + + sale_line_tree + + + sale.line + + sale_line_tree_sequence + sale.create.subscription.start form diff --git a/tryton.cfg b/tryton.cfg index 299a347..d4a5581 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -23,6 +23,7 @@ xml: purchase.xml uom.xml maintenance.xml + move.xml subscription.xml message.xml \ No newline at end of file diff --git a/view/move_form.xml b/view/move_form.xml new file mode 100644 index 0000000..d470067 --- /dev/null +++ b/view/move_form.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/view/move_list_shipment.xml b/view/move_list_shipment.xml new file mode 100644 index 0000000..d189157 --- /dev/null +++ b/view/move_list_shipment.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/view/optical_equipment_form.xml b/view/optical_equipment_form.xml index 42e4c87..294d42f 100644 --- a/view/optical_equipment_form.xml +++ b/view/optical_equipment_form.xml @@ -59,7 +59,11 @@ + + + + diff --git a/view/sale_line_form.xml b/view/sale_line_form.xml index 834c189..6876941 100644 --- a/view/sale_line_form.xml +++ b/view/sale_line_form.xml @@ -10,7 +10,12 @@ expr="/form/notebook/page[@id='general']/label[@name='product']" position="before">