Skip to content

Commit ee482a2

Browse files
committed
records_management/
├── __init__.py ├── __manifest__.py ├── controllers/ │ ├── __init__.py │ └── portal.py ├── models/ │ ├── __init__.py │ ├── stock_production_lot.py │ ├── shredding_service.py │ └── pickup_request.py
1 parent aa3fadb commit ee482a2

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed

SCRM_Records-Management.py

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
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

Comments
 (0)