1+ from . import stock_production_lot
2+ from . import shredding_service
3+ from . import pickup_request
4+ from . import stock_picking
5+ from odoo import fields , models
6+ from odoo import fields , models , api
7+ from odoo import fields , models
8+ from odoo import api , models
9+ from . import portal
10+ from odoo import http
11+ from odoo .http import request
12+ from collections import defaultdict
13+
14+ # records_management/__manifest__.py
15+ {
16+ 'name' : 'Records Management' ,
17+ 'version' : '1.0' ,
18+ 'summary' : 'Manage customer inventory and shredding services' ,
19+ 'depends' : ['stock' , 'sale' , 'website' , 'maintenance' ],
20+ 'data' : [
21+ 'security/groups.xml' ,
22+ 'security/ir.model.access.csv' ,
23+ 'data/products.xml' ,
24+ 'data/scheduled_actions.xml' ,
25+ 'views/shredding_views.xml' ,
26+ 'views/pickup_views.xml' ,
27+ 'views/templates.xml' ,
28+ ],
29+ 'assets' : {
30+ 'web.assets_backend' : [
31+ 'records_management/static/src/js/map_widget.js' ,
32+ ],
33+ },
34+ 'installable' : True ,
35+ 'auto_install' : False ,
36+ } # type: ignore
37+
38+ # records_management/models/__init__.py
39+
40+ # records_management/models/stock_production_lot.py
41+
42+ class StockProductionLot (models .Model ):
43+ _inherit = 'stock.production.lot'
44+
45+ customer_id = fields .Many2one ('res.partner' , string = 'Customer' )
46+
47+ # records_management/models/shredding_service.py
48+
49+ class ShreddingService (models .Model ):
50+ _name = 'shredding.service'
51+ _description = 'Document Shredding Service'
52+
53+ customer_id = fields .Many2one ('res.partner' , string = 'Customer' , required = True )
54+ service_date = fields .Date (string = 'Service Date' , default = fields .Date .today )
55+ service_type = fields .Selection ([
56+ ('bin' , 'Bin Shredding' ),
57+ ('box' , 'Box Shredding' )
58+ ], string = 'Service Type' , required = True )
59+ bin_ids = fields .Many2many ('stock.production.lot' , string = 'Serviced Bins' ,
60+ domain = [('product_id.name' , '=' , 'Shredding Bin' )])
61+ box_quantity = fields .Integer (string = 'Number of Boxes' )
62+ shredded_box_ids = fields .Many2many ('stock.production.lot' , string = 'Shredded Boxes' ,
63+ domain = [('customer_id' , '!=' , False )])
64+ audit_barcodes = fields .Text (string = 'Audit Barcodes' )
65+ total_charge = fields .Float (string = 'Total Charge' , compute = '_compute_total_charge' )
66+ timestamp = fields .Datetime (string = 'Service Timestamp' , default = fields .Datetime .now )
67+ latitude = fields .Float (string = 'Latitude' )
68+ longitude = fields .Float (string = 'Longitude' )
69+ attachment_ids = fields .Many2many ('ir.attachment' , string = 'Attachments' )
70+ map_display = fields .Char (compute = '_compute_map_display' , string = 'Map' )
71+
72+ @api .depends ('service_type' , 'bin_ids' , 'box_quantity' , 'shredded_box_ids' )
73+ def _compute_total_charge (self ):
74+ for record in self :
75+ if record .service_type == 'bin' :
76+ record .total_charge = len (record .bin_ids ) * 10.0
77+ else :
78+ qty = record .box_quantity or len (record .shredded_box_ids )
79+ record .total_charge = qty * 5.0
80+
81+ def _compute_map_display (self ):
82+ for record in self :
83+ record .map_display = f"{ record .latitude } ,{ record .longitude } "
84+
85+ # records_management/models/pickup_request.py
86+
87+ class PickupRequest (models .Model ):
88+ _name = 'pickup.request'
89+ _description = 'Pickup Request'
90+
91+ customer_id = fields .Many2one ('res.partner' , string = 'Customer' , required = True )
92+ request_date = fields .Date (string = 'Request Date' , default = fields .Date .today )
93+ state = fields .Selection ([
94+ ('draft' , 'Draft' ),
95+ ('confirmed' , 'Confirmed' ),
96+ ('done' , 'Done' )
97+ ], default = 'draft' , string = 'Status' )
98+ item_ids = fields .Many2many ('stock.production.lot' , string = 'Items' ,
99+ domain = "[('customer_id', '=', customer_id)]" )
100+
101+ # records_management/models/stock_picking.py
102+
103+ class StockPicking (models .Model ):
104+ _inherit = 'stock.picking'
105+
106+ def button_validate (self ):
107+ res = super ().button_validate ()
108+ if self .state == 'done' and self .picking_type_id .code == 'outgoing' :
109+ customer_items = self .move_line_ids .filtered (lambda ml : ml .lot_id .customer_id )
110+ if customer_items :
111+ customer = customer_items [0 ].lot_id .customer_id
112+ self .env ['sale.order' ].create ({
113+ 'partner_id' : customer .id ,
114+ 'order_line' : [(0 , 0 , {
115+ 'product_id' : self .env .ref ('records_management.retrieval_fee_product' ).id ,
116+ 'product_uom_qty' : len (customer_items ),
117+ })],
118+ })
119+ return res
120+
121+ # records_management/controllers/__init__.py
122+
123+ # records_management/controllers/portal.py
124+
125+ class InventoryPortal (http .Controller ):
126+ @http .route ('/my/inventory' , type = 'http' , auth = 'user' , website = True )
127+ def inventory (self , ** kw ):
128+ partner = request .env .user .partner_id
129+ serials = request .env ['stock.production.lot' ].search ([('customer_id' , '=' , partner .id )])
130+ quants = request .env ['stock.quant' ].search ([
131+ ('lot_id' , 'in' , serials .ids ),
132+ ('location_id.usage' , '=' , 'internal' )
133+ ])
134+ return request .render ('records_management.inventory_template' , {'quants' : quants })
135+
136+ @http .route ('/my/request_pickup' , type = 'http' , auth = 'user' , website = True , methods = ['POST' ])
137+ def request_pickup (self , ** post ):
138+ partner = request .env .user .partner_id
139+ item_ids = [int (id ) for id in request .httprequest .form .getlist ('item_ids' )]
140+ items = request .env ['stock.production.lot' ].search ([
141+ ('id' , 'in' , item_ids ),
142+ ('customer_id' , '=' , partner .id )
143+ ])
144+ if items :
145+ request .env ['pickup.request' ].create ({
146+ 'customer_id' : partner .id ,
147+ 'item_ids' : [(6 , 0 , items .ids )],
148+ })
149+ return request .redirect ('/my/inventory' )
150+
151+ # records_management/views/templates.xml
152+ < odoo >
153+ < template id = "inventory_template" name = "My Inventory" >
154+ < t t - call = "website.layout" >
155+ < div class = "container" >
156+ < h2 > My Inventory < / h2 >
157+ < form action = "/my/request_pickup" method = "post" >
158+ < table class = "table table-striped" >
159+ < thead >
160+ < tr >
161+ < th > Select < / th >
162+ < th > Product < / th >
163+ < th > Serial Number < / th >
164+ < th > Location < / th >
165+ < / tr >
166+ < / thead >
167+ < tbody >
168+ < t t - foreach = "quants" t - as = "quant" >
169+ < tr >
170+ < td >
171+ < input type = "checkbox" name = "item_ids" t - att - value = "quant.lot_id.id" / >
172+ < / td >
173+ < td > < t t - esc = "quant.product_id.display_name" / > < / td >
174+ < td > < t t - esc = "quant.lot_id.name" / > < / td >
175+ < td > < t t - esc = "quant.location_id.display_name" / > < / td >
176+ < / tr >
177+ < / t >
178+ < / tbody >
179+ < / table >
180+ < button type = "submit" class = "btn btn-primary" > Request Check - Out for Selected Items < / button >
181+ < / form >
182+ < / div >
183+ < / t >
184+ < / template >
185+ < / odoo >
186+
187+ # records_management/views/shredding_views.xml
188+ < odoo >
189+ < record id = "view_shredding_service_form" model = "ir.ui.view" >
190+ < field name = "name" > shredding .service .form < / field >
191+ < field name = "model" > shredding .service < / field >
192+ < field name = "arch" type = "xml" >
193+ < form >
194+ < sheet >
195+ < group >
196+ < field name = "customer_id" / >
197+ < field name = "service_date" / >
198+ < field name = "service_type" / >
199+ < field name = "bin_ids" / >
200+ < field name = "box_quantity" / >
201+ < field name = "shredded_box_ids" / >
202+ < field name = "audit_barcodes" / >
203+ < field name = "total_charge" readonly = "1" / >
204+ < field name = "timestamp" / >
205+ < field name = "latitude" / >
206+ < field name = "longitude" / >
207+ < field name = "attachment_ids" widget = "many2many_binary" / >
208+ < field name = "map_display" widget = "map_widget" options = "{'latitude_field': 'latitude', 'longitude_field': 'longitude'}" / >
209+ < / group >
210+ < / sheet >
211+ < / form >
212+ < / field >
213+ < / record >
214+ < / odoo >
215+
216+ # records_management/views/pickup_views.xml
217+ < odoo >
218+ < record id = "view_pickup_request_form" model = "ir.ui.view" >
219+ < field name = "name" > pickup .request .form < / field >
220+ < field name = "model" > pickup .request < / field >
221+ < field name = "arch" type = "xml" >
222+ < form >
223+ < sheet >
224+ < group >
225+ < field name = "customer_id" / >
226+ < field name = "request_date" / >
227+ < field name = "state" / >
228+ < field name = "item_ids" / >
229+ < / group >
230+ < / sheet >
231+ < / form >
232+ < / field >
233+ < / record >
234+ < / odoo >
235+
236+ # records_management/data/products.xml
237+ < odoo >
238+ < record id = "box_product" model = "product.product" >
239+ < field name = "name" > Box < / field >
240+ < field name = "type" > product < / field >
241+ < field name = "tracking" > serial < / field >
242+ < / record >
243+ < record id = "file_product" model = "product.product" >
244+ < field name = "name" > File < / field >
245+ < field name = "type" > product < / field >
246+ < field name = "tracking" > serial < / field >
247+ < / record >
248+ < record id = "shredding_bin_product" model = "product.product" >
249+ < field name = "name" > Shredding Bin < / field >
250+ < field name = "type" > product < / field >
251+ < field name = "tracking" > serial < / field >
252+ < / record >
253+ < record id = "retrieval_fee_product" model = "product.product" >
254+ < field name = "name" > Retrieval Fee < / field >
255+ < field name = "type" > service < / field >
256+ < field name = "list_price" > 10.0 < / field >
257+ < / record >
258+ < record id = "storage_fee_product" model = "product.product" >
259+ < field name = "name" > Storage Fee < / field >
260+ < field name = "type" > service < / field >
261+ < field name = "list_price" > 5.0 < / field >
262+ < / record >
263+ < / odoo >
264+
265+ # records_management/data/scheduled_actions.xml
266+ < odoo >
267+ < record id = "ir_cron_storage_fees" model = "ir.cron" >
268+ < field name = "name" > Compute Monthly Storage Fees < / field >
269+ < field name = "model_id" ref = "base.model_ir_cron" / >
270+ < field name = "state" > code < / field >
271+ < field name = "code" >
272+ quants = env ['stock.quant' ].search ([('location_id.usage' , '=' , 'internal' ), ('lot_id.customer_id' , '!=' , False )])
273+ customer_items = defaultdict (int )
274+ for quant in quants :
275+ customer = quant .lot_id .customer_id
276+ if customer :
277+ customer_items [customer ] += 1
278+ for customer , qty in customer_items .items ():
279+ env ['sale.order' ].create ({
280+ 'partner_id' : customer .id ,
281+ 'order_line' : [(0 , 0 , {
282+ 'product_id' : env .ref ('records_management.storage_fee_product' ).id ,
283+ 'product_uom_qty' : qty ,
284+ })],
285+ })
286+ < / field >
287+ < field name = "interval_number" > 1 < / field >
288+ < field name = "interval_type" > months < / field >
289+ < field name = "numbercall" > - 1 < / field >
290+ < / record >
291+ < / odoo >
292+
293+ # records_management/security/groups.xml
294+ < odoo >
295+ < record id = "group_warehouse_manager" model = "res.groups" >
296+ < field name = "name" > Warehouse Manager < / field >
297+ < / record >
298+ < record id = "group_shredding_technician" model = "res.groups" >
299+ < field name = "name" > Shredding Technician < / field >
300+ < / record >
301+ < record id = "group_customer_service" model = "res.groups" >
302+ < field name = "name" > Customer Service < / field >
303+ < / record >
304+ < / odoo >
305+
306+ # records_management/security/ir.model.access.csv
307+ id ,name ,model_id :id ,group_id :id ,perm_read ,perm_write ,perm_create ,perm_unlink
308+ access_stock_production_lot_manager ,access .stock .production .lot .manager ,model_stock_production_lot ,group_warehouse_manager ,1 ,1 ,1 ,1
309+ access_stock_production_lot_cs ,access .stock .production .lot .cs ,model_stock_production_lot ,group_customer_service ,1 ,0 ,0 ,0
310+ access_shredding_service_tech ,access .shredding .service .tech ,model_shredding_service ,group_shredding_technician ,1 ,1 ,1 ,1
311+ access_shredding_service_cs ,access .shredding .service .cs ,model_shredding_service ,group_customer_service ,1 ,0 ,0 ,0
312+ access_pickup_request_cs ,access .pickup .request .cs ,model_pickup_request ,group_customer_service ,1 ,1 ,1 ,1
313+ access_pickup_request_manager ,access .pickup .request .manager ,model_pickup_request ,group_warehouse_manager ,1 ,0 ,0 ,0
314+
315+ # records_management/static/src/js/map_widget.js
316+ odoo .define ('records_management.map_widget' , function (require ) {
317+ "use strict" ;
318+
319+ var AbstractField = require ('web.AbstractField' );
320+ var fieldRegistry = require ('web.field_registry' );
321+
322+ var MapWidget = AbstractField .extend ({
323+ className : 'map_widget' ,
324+ _render : function () {
325+ this .$el .html ('<div class="map_container" style="width:100%;height:300px"></div>' );
326+ var latitude = this .recordData [this .options .latitude_field ];
327+ var longitude = this .recordData [this .options .longitude_field ];
328+ if (latitude & & longitude & & window .google & & window .google .maps ) {
329+ var map = new google .maps .Map (this .$('.map_container' )[0 ], {
330+ center : { lat : latitude , lng : longitude },
331+ zoom : 15
332+ });
333+ new google .maps .Marker ({
334+ position : { lat : latitude , lng : longitude },
335+ map : map
336+ });
337+ } else {
338+ this .$('.map_container' ).html ('<span>No location set</span>' );
339+ }
340+ return this ._super ();
341+ }
342+ });
343+
344+ fieldRegistry .add ('map_widget' , MapWidget );
345+
346+ return MapWidget ;
347+ });
0 commit comments