• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been set to done!

OCA / vertical-ngo / 325

pending completion
325

Pull #62

travis-ci

yvaucher
Do not set manually sourced on Cost estimate lines when LRS is Other or WH Dispatch
Pull Request #62: Do not set manually sourced on Cost estimate lines when LRS is Other or ...

2 of 2 new or added lines in 1 file covered. (100.0%)

1070 of 1334 relevant lines covered (80.21%)

2.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.85
/framework_agreement_sourcing/model/logistic_requisition.py
1
# -*- coding: utf-8 -*-
2
##############################################################################
3
#
4
#    Author: Nicolas Bessi
5
#    Copyright 2013 Camptocamp SA
6
#
7
#    This program is free software: you can redistribute it and/or modify
8
#    it under the terms of the GNU Affero General Public License as
9
#    published by the Free Software Foundation, either version 3 of the
10
#    License, or (at your option) any later version.
11
#
12
#    This program is distributed in the hope that it will be useful,
13
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
#    GNU Affero General Public License for more details.
16
#
17
#    You should have received a copy of the GNU Affero General Public License
18
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
#
20
##############################################################################
21
from collections import namedtuple
2✔
22
from openerp.osv import orm
2✔
23

24

25
class logistic_requisition_line(orm.Model):
2✔
26

27
    """Override to enable generation of source line"""
28

29
    _inherit = "logistic.requisition.line"
2✔
30

31
    def _prepare_line_source(self, cr, uid, line,
2✔
32
                             qty=None, agreement=None,
33
                             context=None):
34
        """Prepare data dict for source line creation. If an agreement
35
        is given, the procurement_method will be an LTA ('fw_agreement').
36
        Otherwise, if it's a stockable product we'll go to tender
37
        by setting procurement_method as 'procurement'. Finally marke the
38
        rest as 'other'. Those are default value that can be changed afterward
39
        by the user.
40

41
        :params line: browse record of origin requistion.line
42
        :params agreement: browse record of origin agreement
43
        :params qty: quantity to be set on source line
44

45
        :returns: dict to be used by Model.create
46

47
        """
48
        res = {}
2✔
49
        res['proposed_product_id'] = line.product_id.id
2✔
50
        res['requisition_line_id'] = line.id
2✔
51
        res['proposed_uom_id'] = line.requested_uom_id.id
2✔
52
        res['unit_cost'] = 0.0
2✔
53
        res['proposed_qty'] = qty
2✔
54
        res['framework_agreement_id'] = False
2✔
55
        res['price_is'] = 'fixed'
2✔
56
        if agreement:
2✔
57
            if not agreement.product_id.id == line.product_id.id:
2✔
58
                raise ValueError(
×
59
                    "Product mismatch for agreement and requisition line")
60
            res['framework_agreement_id'] = agreement.id
2✔
61
            res['procurement_method'] = 'fw_agreement'
62
        else:
63
            if line.product_id.type == 'product':
64
                res['procurement_method'] = 'procurement'
65
            else:
66
                res['procurement_method'] = 'other'
2✔
67
        return res
2✔
68

69
    def _sort_agreements(self, cr, uid, agreements, qty, currency=None,
2✔
70
                         context=None):
2✔
71
        """Sort agreements to be proposed
72

2✔
73
        Agreement with negociated currency will first be taken in account
74
        then they will be choosen by price converted in currency company
75

76
        :param agreements: list of agreements to be sorted
77
        :param currency: prefered currrency
78

79
        :returns: sorted agreements list
80

81
        """
82
        if not agreements:
83
            return agreements
84

85
        def _best_company_price(cr, uid, agreement, qty):
2✔
86
            """Returns the best price in company currency
2✔
87

88
            For given agreement and price
2✔
89

90
            """
91
            comp_id = self.pool['framework.agreement']._company_get(cr, uid)
92
            comp_obj = self.pool['res.company']
93
            currency_obj = self.pool['res.currency']
94
            comp_currency = comp_obj.browse(cr, uid, comp_id,
2✔
95
                                            context=context).currency_id
2✔
96
            prices = []
2✔
97
            for pl in agreement.framework_agreement_pricelist_ids:
2✔
98
                price = agreement.get_price(qty, currency=pl.currency_id)
99
                comp_price = currency_obj.compute(cr, uid,
2✔
100
                                                  pl.currency_id.id,
2✔
101
                                                  comp_currency.id,
2✔
102
                                                  price, False)
2✔
103
                prices.append(comp_price)
104
            return min(prices)
105

106
        firsts = []
2✔
107
        if currency:
2✔
108
            firsts = [x for x in agreements if x.has_currency(currency)]
109
            lasts = [x for x in agreements if not x.has_currency(currency)]
2✔
110
            firsts.sort(key=lambda x: x.get_price(qty, currency=currency))
2✔
111
            lasts.sort(key=lambda x: _best_company_price(cr, uid, x, qty))
2✔
112
            return firsts + lasts
2✔
113
        else:
2✔
114
            agreements.sort(key=lambda x: _best_company_price(cr, uid, x, qty))
2✔
115
            return agreements
2✔
116

117
    def _generate_lines_from_agreements(self, cr, uid, container, line,
2✔
118
                                        agreements, qty, currency=None,
2✔
119
                                        context=None):
120
        """Generate 1/n source line(s) for one requisition line.
2✔
121

122
        This is done using available agreements.
123
        We first look for cheapeast agreement.
124
        Then if no more quantity are available and there is still remaining
125
        needs we look for next cheapest agreement or return remaining qty.
126
        we prefer to use agreement with negociated currency first even
127
        if they are cheaper in other currences. Then it will choose remaining
128
        agreements ordered by price converted in company currency
129

130
        :param container: list of agreements browse
131
        :param qty: quantity to be sourced
132
        :param line: origin requisition line
133

134
        :returns: remaining quantity to source
135

136
        """
137
        agreements = agreements if agreements is not None else []
138
        agreements = self._sort_agreements(cr, uid, agreements, qty,
139
                                           currency=currency)
140
        if not agreements:
2✔
141
            return qty
2✔
142
        current_agr = agreements.pop(0)
143
        avail = current_agr.available_quantity
2✔
144
        if not avail:
2✔
145
            return qty
2✔
146
        avail_sold = avail - qty
2✔
147
        to_consume = qty if avail_sold >= 0 else avail
2✔
148

×
149
        source_id = self.make_source_line(cr, uid, line, force_qty=to_consume,
2✔
150
                                          agreement=current_agr,
2✔
151
                                          context=context)
152
        container.append(source_id)
2✔
153
        difference = qty - to_consume
154
        if difference:
155
            return self._generate_lines_from_agreements(cr, uid, container,
2✔
156
                                                        line, agreements,
2✔
157
                                                        difference,
2✔
158
                                                        context=context)
2✔
159
        else:
160
            return 0
161

162
    def _source_lines_for_agreements(self, cr, uid, line, agreements,
163
                                     currency=None, context=None):
2✔
164
        """Generate 1/n source line(s) for one requisition line
165

2✔
166
        This is done using available agreements.
167
        We first look for cheapeast agreement.
168
        Then if no more quantity are available and there is still remaining
169
        needs we look for next cheapest agreement or we create a tender source
170
        line
171

172
        :param line: requisition line browse record
173
        :returns: (generated line ids, remaining qty not covered by agreement)
174

175
        """
176
        Sourced = namedtuple('Sourced', ['generated', 'remaining'])
177
        qty = line.requested_qty
178
        generated = []
179
        remaining_qty = self._generate_lines_from_agreements(cr, uid,
2✔
180
                                                             generated, line,
2✔
181
                                                             agreements, qty,
2✔
182
                                                             currency=currency,
2✔
183
                                                             context=context)
184
        return Sourced(generated, remaining_qty)
185

186
    def make_source_line(self, cr, uid, line, force_qty=None, agreement=None,
187
                         context=None):
2✔
188
        """Generate a source line from a requisition line, see
189
        _prepare_line_source for details.
2✔
190

191
        :param line: browse record of origin logistic.request
192
        :param force_qty: if set this quantity will be used instead
193
        of requested quantity
194
        :returns: id of generated source line
195

196
        """
197
        qty = force_qty if force_qty else line.requested_qty
198
        src_obj = self.pool['logistic.requisition.source']
199
        vals = self._prepare_line_source(cr, uid, line,
200
                                         qty=qty,
2✔
201
                                         agreement=agreement,
2✔
202
                                         context=None)
2✔
203
        return src_obj.create(cr, uid, vals, context=context)
204

205
    def _generate_source_line(self, cr, uid, line, context=None):
206
        """Generate one or n source line(s) per requisition line.
2✔
207

208
        Depending on the available resources. If there is framework
2✔
209
        agreement(s) running we generate one or n source line using agreements
210
        otherwise we generate one source line using tender process
211

212
        :param line: browse record of origin logistic.request
213

214
        :returns: list of generated source line ids
215

216
        """
217
        if line.source_ids:
218
            return None
219
        agr_obj = self.pool['framework.agreement']
220
        date = line.requisition_id.date
2✔
221
        currency = line.currency_id
2✔
222
        product_id = line.product_id.id
2✔
223
        agreements = agr_obj.get_all_product_agreements(cr, uid, product_id,
2✔
224
                                                        date,
2✔
225
                                                        context=context)
2✔
226
        generated_lines = []
2✔
227
        if agreements:
228
            line_ids, missing_qty = self._source_lines_for_agreements(
229
                cr, uid, line, agreements, currency=currency)
2✔
230
            generated_lines.extend(line_ids)
2✔
231
            if missing_qty:
2✔
232
                generated_lines.append(self.make_source_line(
233
                    cr, uid, line, force_qty=missing_qty))
2✔
234
        else:
2✔
235
            generated_lines.append(self.make_source_line(cr, uid, line))
2✔
236

237
        return generated_lines
238

2✔
239
    def _do_confirm(self, cr, uid, ids, context=None):
240
        """Override to generate source lines from requision line.
2✔
241

242
        Please refer to _generate_source_line documentation
2✔
243

244
        """
245
        # TODO refactor
246
        # this should probably be in logistic_requisition module
247
        # providing a mechanism to allow each type of sourcing method
248
        # to generate source line
249
        res = super(logistic_requisition_line, self)._do_confirm(
250
            cr, uid, ids, context=context)
251
        for line_br in self.browse(cr, uid, ids, context=context):
252
            self._generate_source_line(cr, uid, line_br, context=context)
2✔
253
        return res
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc