Mark as finished
This commit is contained in:
@@ -215,6 +215,7 @@ def register():
|
|||||||
lot.LotShippingStart,
|
lot.LotShippingStart,
|
||||||
lot.LotPlanTransportStart,
|
lot.LotPlanTransportStart,
|
||||||
lot.LotPlanTransportLine,
|
lot.LotPlanTransportLine,
|
||||||
|
lot.LotMarkFinishedStart,
|
||||||
lot.LotMatchingStart,
|
lot.LotMatchingStart,
|
||||||
lot.LotWeighingStart,
|
lot.LotWeighingStart,
|
||||||
lot.LotAddLot,
|
lot.LotAddLot,
|
||||||
@@ -330,8 +331,9 @@ def register():
|
|||||||
#lot.LotMatchingUnit,
|
#lot.LotMatchingUnit,
|
||||||
lot.LotWeighing,
|
lot.LotWeighing,
|
||||||
lot.CreateContracts,
|
lot.CreateContracts,
|
||||||
lot.LotUnmatch,
|
lot.LotUnmatch,
|
||||||
lot.LotUnship,
|
lot.LotMarkFinished,
|
||||||
|
lot.LotUnship,
|
||||||
lot.LotRemove,
|
lot.LotRemove,
|
||||||
lot.LotInvoice,
|
lot.LotInvoice,
|
||||||
lot.LotAdding,
|
lot.LotAdding,
|
||||||
|
|||||||
@@ -68,6 +68,16 @@ def default_itsa_unit():
|
|||||||
return record_id(unit) if unit else None
|
return record_id(unit) if unit else None
|
||||||
|
|
||||||
|
|
||||||
|
def default_itsa_bulk_unit():
|
||||||
|
unit = _search_first('product.uom', [
|
||||||
|
[('symbol', '=', 'Bulk')],
|
||||||
|
[('name', '=', 'Bulk')],
|
||||||
|
[('symbol', '=', 'bulk')],
|
||||||
|
[('name', '=', 'bulk')],
|
||||||
|
])
|
||||||
|
return record_id(unit) if unit else None
|
||||||
|
|
||||||
|
|
||||||
def _company_from_values(values):
|
def _company_from_values(values):
|
||||||
company = values.get('company')
|
company = values.get('company')
|
||||||
if company and hasattr(company, 'party'):
|
if company and hasattr(company, 'party'):
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import logging
|
|||||||
from trytond.exceptions import UserWarning, UserError
|
from trytond.exceptions import UserWarning, UserError
|
||||||
from trytond.modules.purchase_trade.service import ContractFactory
|
from trytond.modules.purchase_trade.service import ContractFactory
|
||||||
from trytond.modules.purchase_trade.company_defaults import (
|
from trytond.modules.purchase_trade.company_defaults import (
|
||||||
default_itsa_unit, is_itsa_company)
|
default_itsa_bulk_unit, default_itsa_unit, is_itsa_company)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -1689,11 +1689,51 @@ class LotQt(
|
|||||||
affected_lines.append(lqt.lot_s.sale_line)
|
affected_lines.append(lqt.lot_s.sale_line)
|
||||||
return affected_lines
|
return affected_lines
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unmatch_lotqt(cls, lqt):
|
||||||
|
if not lqt or not lqt.lot_p or not lqt.lot_s:
|
||||||
|
return []
|
||||||
|
lot = lqt.lot_p
|
||||||
|
qt = lqt.lot_quantity
|
||||||
|
if not lot.updateVirtualPart(qt, lqt.lot_shipment_origin, None):
|
||||||
|
lot.createVirtualPart(qt, lqt.lot_shipment_origin, None)
|
||||||
|
if not lot.updateVirtualPart(
|
||||||
|
qt, lqt.lot_shipment_origin, lqt.lot_s, 'only sale'):
|
||||||
|
lot.createVirtualPart(
|
||||||
|
qt, lqt.lot_shipment_origin, lqt.lot_s, 'only sale')
|
||||||
|
lot.updateVirtualPart(-qt, lqt.lot_shipment_origin, lqt.lot_s)
|
||||||
|
return cls._affected_lines_for_lqt(lqt)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mark_lines_finished_from_lotqt(cls, lqt, mark_purchase=False,
|
||||||
|
mark_sale=False, unmatch=False):
|
||||||
|
if not mark_purchase and not mark_sale:
|
||||||
|
return []
|
||||||
|
|
||||||
|
affected_lines = []
|
||||||
|
if mark_purchase and lqt.lot_p and lqt.lot_p.line:
|
||||||
|
purchase_line = lqt.lot_p.line
|
||||||
|
purchase_line.finished = True
|
||||||
|
Pool().get('purchase.line').save([purchase_line])
|
||||||
|
affected_lines.append(purchase_line)
|
||||||
|
if mark_sale and lqt.lot_s and lqt.lot_s.sale_line:
|
||||||
|
sale_line = lqt.lot_s.sale_line
|
||||||
|
sale_line.finished = True
|
||||||
|
Pool().get('sale.line').save([sale_line])
|
||||||
|
affected_lines.append(sale_line)
|
||||||
|
if unmatch:
|
||||||
|
if not lqt.lot_p or not lqt.lot_s:
|
||||||
|
raise UserError(
|
||||||
|
"Mark as finished is only available on matched virtual "
|
||||||
|
"lines.")
|
||||||
|
affected_lines.extend(cls.unmatch_lotqt(lqt))
|
||||||
|
return affected_lines
|
||||||
|
|
||||||
def isVirtualP(self):
|
def isVirtualP(self):
|
||||||
if self.lot_p:
|
if self.lot_p:
|
||||||
Lot = Pool().get('lot.lot')
|
Lot = Pool().get('lot.lot')
|
||||||
l = Lot(self.lot_p)
|
l = Lot(self.lot_p)
|
||||||
return (l.lot_type == 'virtual')
|
return (l.lot_type == 'virtual')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def isVirtualS(self):
|
def isVirtualS(self):
|
||||||
@@ -3439,7 +3479,7 @@ class LotShipping(Wizard):
|
|||||||
if self.ship.shipment != 'in':
|
if self.ship.shipment != 'in':
|
||||||
raise UserError("Shipment creation is only available for Shipment In.")
|
raise UserError("Shipment creation is only available for Shipment In.")
|
||||||
if not self.ship.shipment_supplier:
|
if not self.ship.shipment_supplier:
|
||||||
raise UserError("Shipment supplier is required.")
|
raise UserError("Broker is required.")
|
||||||
values = {
|
values = {
|
||||||
'supplier': self._record_id(self.ship.shipment_supplier),
|
'supplier': self._record_id(self.ship.shipment_supplier),
|
||||||
}
|
}
|
||||||
@@ -3447,6 +3487,9 @@ class LotShipping(Wizard):
|
|||||||
values['from_location'] = self.ship.planned_from_location.id
|
values['from_location'] = self.ship.planned_from_location.id
|
||||||
if self.ship.planned_to_location:
|
if self.ship.planned_to_location:
|
||||||
values['to_location'] = self.ship.planned_to_location.id
|
values['to_location'] = self.ship.planned_to_location.id
|
||||||
|
vessel = getattr(self.ship, 'shipment_vessel', None)
|
||||||
|
if vessel:
|
||||||
|
values['vessel'] = vessel.id
|
||||||
bl_number = getattr(self.ship, 'shipment_bl_number', None)
|
bl_number = getattr(self.ship, 'shipment_bl_number', None)
|
||||||
bl_date = getattr(self.ship, 'shipment_bl_date', None)
|
bl_date = getattr(self.ship, 'shipment_bl_date', None)
|
||||||
if bl_number:
|
if bl_number:
|
||||||
@@ -3563,12 +3606,16 @@ class LotShippingStart(ModelView):
|
|||||||
"Create a new shipment",
|
"Create a new shipment",
|
||||||
states={'invisible': Eval('shipment') != 'in'})
|
states={'invisible': Eval('shipment') != 'in'})
|
||||||
shipment_supplier = fields.Many2One(
|
shipment_supplier = fields.Many2One(
|
||||||
'party.party', "Shipment supplier",
|
'party.party', "Broker",
|
||||||
states={
|
states={
|
||||||
'invisible': ~Bool(Eval('create_new_shipment')),
|
'invisible': ~Bool(Eval('create_new_shipment')),
|
||||||
'required': Bool(Eval('create_new_shipment')),
|
'required': Bool(Eval('create_new_shipment')),
|
||||||
},
|
},
|
||||||
depends=['create_new_shipment'])
|
depends=['create_new_shipment'])
|
||||||
|
shipment_vessel = fields.Many2One(
|
||||||
|
'trade.vessel', "Vessel",
|
||||||
|
states={'invisible': ~Bool(Eval('create_new_shipment'))},
|
||||||
|
depends=['create_new_shipment'])
|
||||||
planned_from_location = fields.Many2One(
|
planned_from_location = fields.Many2One(
|
||||||
'stock.location', "Planned From", readonly=True)
|
'stock.location', "Planned From", readonly=True)
|
||||||
planned_to_location = fields.Many2One(
|
planned_to_location = fields.Many2One(
|
||||||
@@ -4161,9 +4208,9 @@ class LotMatchingLot(ModelView):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class LotUnmatch(Wizard):
|
class LotUnmatch(Wizard):
|
||||||
"Unmatch"
|
"Unmatch"
|
||||||
__name__ = "lot.unmatch"
|
__name__ = "lot.unmatch"
|
||||||
|
|
||||||
start = StateTransition()
|
start = StateTransition()
|
||||||
|
|
||||||
@@ -4176,20 +4223,7 @@ class LotUnmatch(Wizard):
|
|||||||
if r.id > 10000000 :
|
if r.id > 10000000 :
|
||||||
lqt = LotQt(r.id - 10000000)
|
lqt = LotQt(r.id - 10000000)
|
||||||
if lqt.lot_p and lqt.lot_s:
|
if lqt.lot_p and lqt.lot_s:
|
||||||
lot = lqt.lot_p
|
affected_lines.extend(LotQt.unmatch_lotqt(lqt))
|
||||||
qt = lqt.lot_quantity
|
|
||||||
#Increase forecasted virtual parts non matched
|
|
||||||
if not lot.updateVirtualPart(qt,lqt.lot_shipment_origin,None):
|
|
||||||
lot.createVirtualPart(qt,lqt.lot_shipment_origin,None)
|
|
||||||
if not lot.updateVirtualPart(qt,lqt.lot_shipment_origin,lqt.lot_s,'only sale'):
|
|
||||||
lot.createVirtualPart(qt,lqt.lot_shipment_origin,lqt.lot_s,'only sale')
|
|
||||||
#Decrease forecasted virtual part matched
|
|
||||||
logger.info("UNMATCH_QT:%s",qt)
|
|
||||||
lot.updateVirtualPart(-qt,lqt.lot_shipment_origin,lqt.lot_s)
|
|
||||||
if lqt.lot_p and lqt.lot_p.line:
|
|
||||||
affected_lines.append(lqt.lot_p.line)
|
|
||||||
if lqt.lot_s and lqt.lot_s.sale_line:
|
|
||||||
affected_lines.append(lqt.lot_s.sale_line)
|
|
||||||
else:
|
else:
|
||||||
lot = Lot(r.id)
|
lot = Lot(r.id)
|
||||||
qt = lot.get_current_quantity_converted()
|
qt = lot.get_current_quantity_converted()
|
||||||
@@ -4211,12 +4245,65 @@ class LotUnmatch(Wizard):
|
|||||||
Lot.assert_lines_quantity_consistency(affected_lines)
|
Lot.assert_lines_quantity_consistency(affected_lines)
|
||||||
return 'end'
|
return 'end'
|
||||||
|
|
||||||
def end(self):
|
def end(self):
|
||||||
return 'reload'
|
return 'reload'
|
||||||
|
|
||||||
|
class LotMarkFinished(Wizard):
|
||||||
|
"Mark as finished"
|
||||||
|
__name__ = "lot.mark_finished"
|
||||||
|
|
||||||
|
start = StateView(
|
||||||
|
'lot.mark_finished.start',
|
||||||
|
'purchase_trade.lot_mark_finished_start_view_form', [
|
||||||
|
Button("Cancel", 'end', 'tryton-cancel'),
|
||||||
|
Button("Apply", 'marking', 'tryton-ok', default=True),
|
||||||
|
])
|
||||||
|
|
||||||
|
marking = StateTransition()
|
||||||
|
|
||||||
|
def transition_marking(self):
|
||||||
|
if not (self.start.mark_purchase_line_finished
|
||||||
|
or self.start.mark_sale_line_finished):
|
||||||
|
raise UserError("Select at least one line side to mark.")
|
||||||
|
Lot = Pool().get('lot.lot')
|
||||||
|
LotQt = Pool().get('lot.qt')
|
||||||
|
affected_lines = []
|
||||||
|
with Lot.skip_quantity_consistency():
|
||||||
|
for record in self.records:
|
||||||
|
if record.id <= 10000000:
|
||||||
|
raise UserError(
|
||||||
|
"Mark as finished is only available on matched "
|
||||||
|
"virtual lines.")
|
||||||
|
lqt = LotQt(record.id - 10000000)
|
||||||
|
if not lqt.lot_p or not lqt.lot_s:
|
||||||
|
raise UserError(
|
||||||
|
"Mark as finished is only available on matched "
|
||||||
|
"virtual lines.")
|
||||||
|
affected_lines.extend(LotQt.mark_lines_finished_from_lotqt(
|
||||||
|
lqt,
|
||||||
|
self.start.mark_purchase_line_finished,
|
||||||
|
self.start.mark_sale_line_finished,
|
||||||
|
unmatch=True))
|
||||||
|
Lot.assert_lines_quantity_consistency(affected_lines)
|
||||||
|
return 'end'
|
||||||
|
|
||||||
|
def end(self):
|
||||||
|
return 'reload'
|
||||||
|
|
||||||
|
|
||||||
|
class LotMarkFinishedStart(ModelView):
|
||||||
|
"Mark as finished"
|
||||||
|
__name__ = "lot.mark_finished.start"
|
||||||
|
|
||||||
|
mark_purchase_line_finished = fields.Boolean(
|
||||||
|
"Mark purchase line as finished")
|
||||||
|
mark_sale_line_finished = fields.Boolean(
|
||||||
|
"Mark sale line as finished")
|
||||||
|
|
||||||
|
|
||||||
class LotUnship(Wizard):
|
class LotUnship(Wizard):
|
||||||
"Unlink transport"
|
"Unlink transport"
|
||||||
__name__ = "lot.unship"
|
__name__ = "lot.unship"
|
||||||
|
|
||||||
start = StateTransition()
|
start = StateTransition()
|
||||||
|
|
||||||
@@ -4390,6 +4477,10 @@ class LotAdding(Wizard):
|
|||||||
'stock.shipment.in,%s' % shipment.id)
|
'stock.shipment.in,%s' % shipment.id)
|
||||||
LotQt.add_physical_lots(
|
LotQt.add_physical_lots(
|
||||||
lqt, self.add.lots, self.add.unlink_remainder)
|
lqt, self.add.lots, self.add.unlink_remainder)
|
||||||
|
LotQt.mark_lines_finished_from_lotqt(
|
||||||
|
lqt,
|
||||||
|
getattr(self.add, 'mark_purchase_line_finished', False),
|
||||||
|
getattr(self.add, 'mark_sale_line_finished', False))
|
||||||
return 'end'
|
return 'end'
|
||||||
|
|
||||||
def end(self):
|
def end(self):
|
||||||
@@ -4401,6 +4492,10 @@ class LotAddLot(ModelView):
|
|||||||
unit = fields.Many2One('product.uom',"Unit",readonly=True)
|
unit = fields.Many2One('product.uom',"Unit",readonly=True)
|
||||||
quantity = fields.Numeric("Quantity available",digits='unit',readonly=True)
|
quantity = fields.Numeric("Quantity available",digits='unit',readonly=True)
|
||||||
unlink_remainder = fields.Boolean("Unlink remaining quantity")
|
unlink_remainder = fields.Boolean("Unlink remaining quantity")
|
||||||
|
mark_purchase_line_finished = fields.Boolean(
|
||||||
|
"Mark purchase line as finished")
|
||||||
|
mark_sale_line_finished = fields.Boolean(
|
||||||
|
"Mark sale line as finished")
|
||||||
transport_missing = fields.Boolean(
|
transport_missing = fields.Boolean(
|
||||||
"Transport missing", readonly=True,
|
"Transport missing", readonly=True,
|
||||||
states={'invisible': ~Eval('transport_missing')})
|
states={'invisible': ~Eval('transport_missing')})
|
||||||
@@ -4443,6 +4538,14 @@ class LotAddLot(ModelView):
|
|||||||
def default_unlink_remainder(cls):
|
def default_unlink_remainder(cls):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default_mark_purchase_line_finished(cls):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default_mark_sale_line_finished(cls):
|
||||||
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_create_new_shipment(cls):
|
def default_create_new_shipment(cls):
|
||||||
return False
|
return False
|
||||||
@@ -4460,18 +4563,30 @@ class LotAddLine(ModelView):
|
|||||||
lot_premium = fields.Numeric("Premium")
|
lot_premium = fields.Numeric("Premium")
|
||||||
lot_chunk_key = fields.Integer("Chunk key")
|
lot_chunk_key = fields.Integer("Chunk key")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default_lot_qt(cls):
|
||||||
|
if is_itsa_company():
|
||||||
|
return Decimal('1')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_lot_unit(cls):
|
def default_lot_unit(cls):
|
||||||
if is_itsa_company():
|
if is_itsa_company():
|
||||||
return default_itsa_unit()
|
return default_itsa_bulk_unit()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_lot_unit_line(cls):
|
def default_lot_unit_line(cls):
|
||||||
if is_itsa_company():
|
if is_itsa_company():
|
||||||
return default_itsa_unit()
|
return default_itsa_unit()
|
||||||
|
|
||||||
# @fields.depends('lot_qt')
|
@fields.depends('lot_quantity', 'lot_gross_quantity')
|
||||||
# def on_change_with_lot_quantity(self):
|
def on_change_lot_quantity(self):
|
||||||
|
if (is_itsa_company()
|
||||||
|
and self.lot_quantity
|
||||||
|
and not self.lot_gross_quantity):
|
||||||
|
self.lot_gross_quantity = self.lot_quantity
|
||||||
|
|
||||||
|
# @fields.depends('lot_qt')
|
||||||
|
# def on_change_with_lot_quantity(self):
|
||||||
# if not self.lot_quantity:
|
# if not self.lot_quantity:
|
||||||
# return self.lot_qt
|
# return self.lot_qt
|
||||||
|
|
||||||
|
|||||||
@@ -245,7 +245,12 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<field name="type">tree</field>
|
<field name="type">tree</field>
|
||||||
<field name="name">lot_weighing_lot_tree</field>
|
<field name="name">lot_weighing_lot_tree</field>
|
||||||
</record>
|
</record>
|
||||||
<record model="ir.action.wizard" id="act_weighing">
|
<record model="ir.ui.view" id="lot_mark_finished_start_view_form">
|
||||||
|
<field name="model">lot.mark_finished.start</field>
|
||||||
|
<field name="type">form</field>
|
||||||
|
<field name="name">lot_mark_finished_start_form</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.action.wizard" id="act_weighing">
|
||||||
<field name="name">📦 Do weighing</field>
|
<field name="name">📦 Do weighing</field>
|
||||||
<field name="wiz_name">lot.weighing</field>
|
<field name="wiz_name">lot.weighing</field>
|
||||||
<field name="model">lot.report</field>
|
<field name="model">lot.report</field>
|
||||||
@@ -389,7 +394,18 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<field name="action" ref="act_unmatch"/>
|
<field name="action" ref="act_unmatch"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.action.wizard" id="act_unship">
|
<record model="ir.action.wizard" id="act_mark_finished">
|
||||||
|
<field name="name">Mark as finished</field>
|
||||||
|
<field name="wiz_name">lot.mark_finished</field>
|
||||||
|
<field name="model">lot.report</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.action.keyword" id="act_mark_finished_keyword">
|
||||||
|
<field name="keyword">form_action</field>
|
||||||
|
<field name="model">lot.report,-1</field>
|
||||||
|
<field name="action" ref="act_mark_finished"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.action.wizard" id="act_unship">
|
||||||
<field name="name">🚢 Unlink transport</field>
|
<field name="name">🚢 Unlink transport</field>
|
||||||
<field name="wiz_name">lot.unship</field>
|
<field name="wiz_name">lot.unship</field>
|
||||||
<field name="model">lot.report</field>
|
<field name="model">lot.report</field>
|
||||||
|
|||||||
@@ -381,6 +381,68 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
company_defaults._book_year_suffix(datetime.date(2026, 4, 1)),
|
company_defaults._book_year_suffix(datetime.date(2026, 4, 1)),
|
||||||
'26')
|
'26')
|
||||||
|
|
||||||
|
def test_itsa_bulk_unit_default_prefers_bulk_uom(self):
|
||||||
|
'ITSA bulk unit default resolves the Bulk UoM'
|
||||||
|
bulk = Mock(id=42)
|
||||||
|
Uom = Mock()
|
||||||
|
Uom.search.side_effect = [[bulk]]
|
||||||
|
pool = Mock()
|
||||||
|
pool.get.return_value = Uom
|
||||||
|
|
||||||
|
with patch.object(company_defaults, 'Pool', return_value=pool):
|
||||||
|
self.assertEqual(company_defaults.default_itsa_bulk_unit(), 42)
|
||||||
|
Uom.search.assert_called_once_with(
|
||||||
|
[('symbol', '=', 'Bulk')], limit=1)
|
||||||
|
|
||||||
|
def test_itsa_add_physical_lot_defaults(self):
|
||||||
|
'ITSA Add Physical lot defaults packing to 1 Bulk and weight unit to Mt'
|
||||||
|
with patch('trytond.modules.purchase_trade.lot.is_itsa_company',
|
||||||
|
return_value=True), patch(
|
||||||
|
'trytond.modules.purchase_trade.lot.default_itsa_bulk_unit',
|
||||||
|
return_value=42), patch(
|
||||||
|
'trytond.modules.purchase_trade.lot.default_itsa_unit',
|
||||||
|
return_value=7):
|
||||||
|
self.assertEqual(
|
||||||
|
lot_module.LotAddLine.default_lot_qt(), Decimal('1'))
|
||||||
|
self.assertEqual(lot_module.LotAddLine.default_lot_unit(), 42)
|
||||||
|
self.assertEqual(lot_module.LotAddLine.default_lot_unit_line(), 7)
|
||||||
|
|
||||||
|
def test_itsa_add_physical_lot_defaults_gross_from_net(self):
|
||||||
|
'ITSA Add Physical lot copies net weight to empty gross weight'
|
||||||
|
line = lot_module.LotAddLine()
|
||||||
|
line.lot_quantity = Decimal('12.500')
|
||||||
|
line.lot_gross_quantity = None
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.lot.is_itsa_company',
|
||||||
|
return_value=True):
|
||||||
|
line.on_change_lot_quantity()
|
||||||
|
|
||||||
|
self.assertEqual(line.lot_gross_quantity, Decimal('12.500'))
|
||||||
|
|
||||||
|
def test_itsa_add_physical_lot_keeps_existing_gross_weight(self):
|
||||||
|
'ITSA Add Physical lot does not overwrite an existing gross weight'
|
||||||
|
line = lot_module.LotAddLine()
|
||||||
|
line.lot_quantity = Decimal('12.500')
|
||||||
|
line.lot_gross_quantity = Decimal('13.000')
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.lot.is_itsa_company',
|
||||||
|
return_value=True):
|
||||||
|
line.on_change_lot_quantity()
|
||||||
|
|
||||||
|
self.assertEqual(line.lot_gross_quantity, Decimal('13.000'))
|
||||||
|
|
||||||
|
def test_non_itsa_add_physical_lot_does_not_default_gross_weight(self):
|
||||||
|
'non-ITSA Add Physical lot does not copy net weight to gross weight'
|
||||||
|
line = lot_module.LotAddLine()
|
||||||
|
line.lot_quantity = Decimal('12.500')
|
||||||
|
line.lot_gross_quantity = None
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.lot.is_itsa_company',
|
||||||
|
return_value=False):
|
||||||
|
line.on_change_lot_quantity()
|
||||||
|
|
||||||
|
self.assertIsNone(line.lot_gross_quantity)
|
||||||
|
|
||||||
@with_transaction()
|
@with_transaction()
|
||||||
def test_purchase_list_fields_use_first_trade_line(self):
|
def test_purchase_list_fields_use_first_trade_line(self):
|
||||||
'purchase list helper fields use the first real purchase line'
|
'purchase list helper fields use the first real purchase line'
|
||||||
@@ -762,9 +824,10 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
self.assertEqual(wizard.default_ship(None), {})
|
self.assertEqual(wizard.default_ship(None), {})
|
||||||
|
|
||||||
def test_lot_shipping_link_creates_shipment_from_dialog_supplier(self):
|
def test_lot_shipping_link_creates_shipment_from_dialog_supplier(self):
|
||||||
'link to transport creates shipment in from dialog supplier'
|
'link to transport creates shipment in from dialog broker'
|
||||||
wizard = lot_module.LotShipping()
|
wizard = lot_module.LotShipping()
|
||||||
supplier = Mock(id=30)
|
supplier = Mock(id=30)
|
||||||
|
vessel = Mock(id=50)
|
||||||
created_shipment = Mock(id=40)
|
created_shipment = Mock(id=40)
|
||||||
ShipmentIn = Mock()
|
ShipmentIn = Mock()
|
||||||
ShipmentIn.create.return_value = [created_shipment]
|
ShipmentIn.create.return_value = [created_shipment]
|
||||||
@@ -776,6 +839,7 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
shipment_internal=None,
|
shipment_internal=None,
|
||||||
create_new_shipment=True,
|
create_new_shipment=True,
|
||||||
shipment_supplier=supplier,
|
shipment_supplier=supplier,
|
||||||
|
shipment_vessel=vessel,
|
||||||
planned_from_location=Mock(id=10),
|
planned_from_location=Mock(id=10),
|
||||||
planned_to_location=Mock(id=20),
|
planned_to_location=Mock(id=20),
|
||||||
)
|
)
|
||||||
@@ -794,10 +858,93 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
'supplier': 30,
|
'supplier': 30,
|
||||||
'from_location': 10,
|
'from_location': 10,
|
||||||
'to_location': 20,
|
'to_location': 20,
|
||||||
|
'vessel': 50,
|
||||||
}])
|
}])
|
||||||
self.assertEqual(wizard.ship.shipment_in, created_shipment)
|
self.assertEqual(wizard.ship.shipment_in, created_shipment)
|
||||||
Lot.assert_lines_quantity_consistency.assert_called_once_with([])
|
Lot.assert_lines_quantity_consistency.assert_called_once_with([])
|
||||||
|
|
||||||
|
def test_lot_shipping_start_uses_broker_label(self):
|
||||||
|
'link to transport uses the shipment in Broker label'
|
||||||
|
self.assertEqual(
|
||||||
|
lot_module.LotShippingStart.shipment_supplier.string, 'Broker')
|
||||||
|
|
||||||
|
def test_lotqt_mark_lines_finished_saves_selected_lines(self):
|
||||||
|
'mark finished helper can finish purchase and sale lines'
|
||||||
|
purchase_line = Mock()
|
||||||
|
sale_line = Mock()
|
||||||
|
lqt = Mock(
|
||||||
|
lot_p=Mock(line=purchase_line),
|
||||||
|
lot_s=Mock(sale_line=sale_line))
|
||||||
|
PurchaseLine = Mock()
|
||||||
|
SaleLine = Mock()
|
||||||
|
pool = Mock()
|
||||||
|
pool.get.side_effect = lambda name: {
|
||||||
|
'purchase.line': PurchaseLine,
|
||||||
|
'sale.line': SaleLine,
|
||||||
|
}[name]
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.lot.Pool',
|
||||||
|
return_value=pool):
|
||||||
|
affected = lot_module.LotQt.mark_lines_finished_from_lotqt(
|
||||||
|
lqt, mark_purchase=True, mark_sale=True)
|
||||||
|
|
||||||
|
self.assertTrue(purchase_line.finished)
|
||||||
|
self.assertTrue(sale_line.finished)
|
||||||
|
PurchaseLine.save.assert_called_once_with([purchase_line])
|
||||||
|
SaleLine.save.assert_called_once_with([sale_line])
|
||||||
|
self.assertEqual(affected, [purchase_line, sale_line])
|
||||||
|
|
||||||
|
def test_lotqt_mark_lines_finished_can_unmatch(self):
|
||||||
|
'mark finished helper can release a matched virtual quantity'
|
||||||
|
purchase_line = Mock()
|
||||||
|
sale_line = Mock()
|
||||||
|
lot_s = Mock(sale_line=sale_line)
|
||||||
|
lot_p = Mock(line=purchase_line)
|
||||||
|
lot_p.updateVirtualPart.side_effect = [False, False, True]
|
||||||
|
lqt = Mock(
|
||||||
|
lot_p=lot_p,
|
||||||
|
lot_s=lot_s,
|
||||||
|
lot_quantity=Decimal('12.5'),
|
||||||
|
lot_shipment_origin='stock.shipment.in,10')
|
||||||
|
PurchaseLine = Mock()
|
||||||
|
pool = Mock()
|
||||||
|
pool.get.return_value = PurchaseLine
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.lot.Pool',
|
||||||
|
return_value=pool):
|
||||||
|
affected = lot_module.LotQt.mark_lines_finished_from_lotqt(
|
||||||
|
lqt, mark_purchase=True, unmatch=True)
|
||||||
|
|
||||||
|
self.assertTrue(purchase_line.finished)
|
||||||
|
PurchaseLine.save.assert_called_once_with([purchase_line])
|
||||||
|
lot_p.createVirtualPart.assert_any_call(
|
||||||
|
Decimal('12.5'), 'stock.shipment.in,10', None)
|
||||||
|
lot_p.createVirtualPart.assert_any_call(
|
||||||
|
Decimal('12.5'), 'stock.shipment.in,10', lot_s, 'only sale')
|
||||||
|
lot_p.updateVirtualPart.assert_any_call(
|
||||||
|
-Decimal('12.5'), 'stock.shipment.in,10', lot_s)
|
||||||
|
self.assertEqual(affected, [
|
||||||
|
purchase_line, purchase_line, sale_line])
|
||||||
|
|
||||||
|
def test_lot_mark_finished_requires_matched_virtual_lotqt(self):
|
||||||
|
'mark finished action rejects non matched rows'
|
||||||
|
wizard = lot_module.LotMarkFinished()
|
||||||
|
wizard.start = Mock(
|
||||||
|
mark_purchase_line_finished=True,
|
||||||
|
mark_sale_line_finished=False)
|
||||||
|
wizard.records = [Mock(id=10000007)]
|
||||||
|
Lot = Mock()
|
||||||
|
Lot.skip_quantity_consistency.return_value.__enter__ = Mock()
|
||||||
|
Lot.skip_quantity_consistency.return_value.__exit__ = Mock()
|
||||||
|
LotQt = Mock(return_value=Mock(lot_p=Mock(), lot_s=None))
|
||||||
|
pool = Mock()
|
||||||
|
pool.get.side_effect = [Lot, LotQt]
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.lot.Pool',
|
||||||
|
return_value=pool):
|
||||||
|
with self.assertRaises(UserError):
|
||||||
|
wizard.transition_marking()
|
||||||
|
|
||||||
def test_lot_shipping_rejects_existing_and_new_shipment(self):
|
def test_lot_shipping_rejects_existing_and_new_shipment(self):
|
||||||
'link to transport rejects choosing existing shipment and create new'
|
'link to transport rejects choosing existing shipment and create new'
|
||||||
wizard = lot_module.LotShipping()
|
wizard = lot_module.LotShipping()
|
||||||
|
|||||||
@@ -2,15 +2,19 @@
|
|||||||
col_widths="1fr,1fr,1fr,1fr,1fr,1fr,1fr,1fr,1fr,1fr,1fr,1fr">
|
col_widths="1fr,1fr,1fr,1fr,1fr,1fr,1fr,1fr,1fr,1fr,1fr,1fr">
|
||||||
<field name="transport_missing" invisible="1"/>
|
<field name="transport_missing" invisible="1"/>
|
||||||
|
|
||||||
<group id="physical_lot_summary" colspan="12" col="6"
|
<group id="physical_lot_summary" colspan="12" col="10"
|
||||||
panel="summary" xalign="0" yalign="0"
|
panel="summary" xalign="0" yalign="0"
|
||||||
col_widths="min-content,160px,min-content,140px,min-content,60px">
|
col_widths="min-content,160px,min-content,140px,min-content,60px,min-content,80px,min-content,80px">
|
||||||
<label name="quantity"/>
|
<label name="quantity"/>
|
||||||
<field name="quantity"/>
|
<field name="quantity"/>
|
||||||
<label name="unit"/>
|
<label name="unit"/>
|
||||||
<field name="unit"/>
|
<field name="unit"/>
|
||||||
<label name="unlink_remainder"/>
|
<label name="unlink_remainder"/>
|
||||||
<field name="unlink_remainder"/>
|
<field name="unlink_remainder"/>
|
||||||
|
<label name="mark_purchase_line_finished"/>
|
||||||
|
<field name="mark_purchase_line_finished"/>
|
||||||
|
<label name="mark_sale_line_finished"/>
|
||||||
|
<field name="mark_sale_line_finished"/>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<group id="physical_lot_transport" string="Transport"
|
<group id="physical_lot_transport" string="Transport"
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<form col="2">
|
||||||
|
<label name="mark_purchase_line_finished"/>
|
||||||
|
<field name="mark_purchase_line_finished"/>
|
||||||
|
<label name="mark_sale_line_finished"/>
|
||||||
|
<field name="mark_sale_line_finished"/>
|
||||||
|
</form>
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
<field name="create_new_shipment"/>
|
<field name="create_new_shipment"/>
|
||||||
<label name="shipment_supplier"/>
|
<label name="shipment_supplier"/>
|
||||||
<field name="shipment_supplier"/>
|
<field name="shipment_supplier"/>
|
||||||
|
<label name="shipment_vessel"/>
|
||||||
|
<field name="shipment_vessel"/>
|
||||||
<label name="shipment_out"/>
|
<label name="shipment_out"/>
|
||||||
<field name="shipment_out"/>
|
<field name="shipment_out"/>
|
||||||
<label name="shipment_internal"/>
|
<label name="shipment_internal"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user